@ionic/core 8.7.12-nightly.20251208 → 8.7.12-nightly.20251209
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/action-sheet.js +186 -5
- package/components/ion-select.js +8 -3
- package/dist/cjs/ion-action-sheet.cjs.entry.js +183 -4
- package/dist/cjs/ion-select_3.cjs.entry.js +8 -3
- package/dist/cjs/ionic.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/action-sheet/action-sheet.js +199 -4
- package/dist/collection/components/select/select.js +8 -3
- package/dist/docs.json +14 -8
- package/dist/esm/ion-action-sheet.entry.js +183 -4
- package/dist/esm/ion-select_3.entry.js +8 -3
- package/dist/esm/ionic.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-5837f29f.entry.js +4 -0
- package/dist/ionic/p-8edc7565.entry.js +4 -0
- package/dist/types/components/action-sheet/action-sheet.d.ts +37 -0
- package/hydrate/index.js +193 -8
- package/hydrate/index.mjs +193 -8
- package/package.json +4 -1
- package/dist/ionic/p-510d86e1.entry.js +0 -4
- package/dist/ionic/p-7380261c.entry.js +0 -4
|
@@ -20,6 +20,7 @@ export class ActionSheet {
|
|
|
20
20
|
this.delegateController = createDelegateController(this);
|
|
21
21
|
this.lockController = createLockController();
|
|
22
22
|
this.triggerController = createTriggerController();
|
|
23
|
+
this.hasRadioButtons = false;
|
|
23
24
|
this.presented = false;
|
|
24
25
|
/** @internal */
|
|
25
26
|
this.hasController = false;
|
|
@@ -64,6 +65,19 @@ export class ActionSheet {
|
|
|
64
65
|
}
|
|
65
66
|
};
|
|
66
67
|
}
|
|
68
|
+
buttonsChanged() {
|
|
69
|
+
const radioButtons = this.getRadioButtons();
|
|
70
|
+
this.hasRadioButtons = radioButtons.length > 0;
|
|
71
|
+
// Initialize activeRadioId when buttons change
|
|
72
|
+
if (this.hasRadioButtons) {
|
|
73
|
+
const checkedButton = radioButtons.find((b) => { var _a; return ((_a = b.htmlAttributes) === null || _a === void 0 ? void 0 : _a['aria-checked']) === 'true'; });
|
|
74
|
+
if (checkedButton) {
|
|
75
|
+
const allButtons = this.getButtons();
|
|
76
|
+
const checkedIndex = allButtons.indexOf(checkedButton);
|
|
77
|
+
this.activeRadioId = this.getButtonId(checkedButton, checkedIndex);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
67
81
|
onIsOpenChange(newValue, oldValue) {
|
|
68
82
|
if (newValue === true && oldValue === false) {
|
|
69
83
|
this.present();
|
|
@@ -144,11 +158,122 @@ export class ActionSheet {
|
|
|
144
158
|
}
|
|
145
159
|
return true;
|
|
146
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Get all buttons regardless of role.
|
|
163
|
+
*/
|
|
147
164
|
getButtons() {
|
|
148
165
|
return this.buttons.map((b) => {
|
|
149
166
|
return typeof b === 'string' ? { text: b } : b;
|
|
150
167
|
});
|
|
151
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Get all radio buttons (buttons with role="radio").
|
|
171
|
+
*/
|
|
172
|
+
getRadioButtons() {
|
|
173
|
+
return this.getButtons().filter((b) => {
|
|
174
|
+
var _a;
|
|
175
|
+
const role = (_a = b.htmlAttributes) === null || _a === void 0 ? void 0 : _a.role;
|
|
176
|
+
return role === 'radio' && !isCancel(role);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Handle radio button selection and update aria-checked state.
|
|
181
|
+
*
|
|
182
|
+
* @param button The radio button that was selected.
|
|
183
|
+
*/
|
|
184
|
+
selectRadioButton(button) {
|
|
185
|
+
const buttonId = this.getButtonId(button);
|
|
186
|
+
// Set the active radio ID (this will trigger a re-render and update aria-checked)
|
|
187
|
+
this.activeRadioId = buttonId;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get or generate an ID for a button.
|
|
191
|
+
*
|
|
192
|
+
* @param button The button for which to get the ID.
|
|
193
|
+
* @param index Optional index of the button in the buttons array.
|
|
194
|
+
* @returns The ID of the button.
|
|
195
|
+
*/
|
|
196
|
+
getButtonId(button, index) {
|
|
197
|
+
if (button.id) {
|
|
198
|
+
return button.id;
|
|
199
|
+
}
|
|
200
|
+
const allButtons = this.getButtons();
|
|
201
|
+
const buttonIndex = index !== undefined ? index : allButtons.indexOf(button);
|
|
202
|
+
return `action-sheet-button-${this.overlayIndex}-${buttonIndex}`;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* When the action sheet has radio buttons, we want to follow the
|
|
206
|
+
* keyboard navigation pattern for radio groups:
|
|
207
|
+
* - Arrow Down/Right: Move to the next radio button (wrap to first if at end)
|
|
208
|
+
* - Arrow Up/Left: Move to the previous radio button (wrap to last if at start)
|
|
209
|
+
* - Space/Enter: Select the focused radio button and trigger its handler
|
|
210
|
+
*/
|
|
211
|
+
onKeydown(ev) {
|
|
212
|
+
// Only handle keyboard navigation if we have radio buttons
|
|
213
|
+
if (!this.hasRadioButtons || !this.presented) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const target = ev.target;
|
|
217
|
+
// Ignore if the target element is not within the action sheet or not a radio button
|
|
218
|
+
if (!this.el.contains(target) ||
|
|
219
|
+
!target.classList.contains('action-sheet-button') ||
|
|
220
|
+
target.getAttribute('role') !== 'radio') {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
// Get all radio button elements and filter out disabled ones
|
|
224
|
+
const radios = Array.from(this.el.querySelectorAll('.action-sheet-button[role="radio"]')).filter((el) => !el.disabled);
|
|
225
|
+
const currentIndex = radios.findIndex((radio) => radio.id === target.id);
|
|
226
|
+
if (currentIndex === -1) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const allButtons = this.getButtons();
|
|
230
|
+
const radioButtons = this.getRadioButtons();
|
|
231
|
+
/**
|
|
232
|
+
* Build a map of button element IDs to their ActionSheetButton
|
|
233
|
+
* config objects.
|
|
234
|
+
* This allows us to quickly look up which button config corresponds
|
|
235
|
+
* to a DOM element when handling keyboard navigation
|
|
236
|
+
* (e.g., whenuser presses Space/Enter or arrow keys).
|
|
237
|
+
* The key is the ID that was set on the DOM element during render,
|
|
238
|
+
* and the value is the ActionSheetButton config that contains the
|
|
239
|
+
* handler and other properties.
|
|
240
|
+
*/
|
|
241
|
+
const buttonIdMap = new Map();
|
|
242
|
+
radioButtons.forEach((b) => {
|
|
243
|
+
const allIndex = allButtons.indexOf(b);
|
|
244
|
+
const buttonId = this.getButtonId(b, allIndex);
|
|
245
|
+
buttonIdMap.set(buttonId, b);
|
|
246
|
+
});
|
|
247
|
+
let nextEl;
|
|
248
|
+
if (['ArrowDown', 'ArrowRight'].includes(ev.key)) {
|
|
249
|
+
ev.preventDefault();
|
|
250
|
+
ev.stopPropagation();
|
|
251
|
+
nextEl = currentIndex === radios.length - 1 ? radios[0] : radios[currentIndex + 1];
|
|
252
|
+
}
|
|
253
|
+
else if (['ArrowUp', 'ArrowLeft'].includes(ev.key)) {
|
|
254
|
+
ev.preventDefault();
|
|
255
|
+
ev.stopPropagation();
|
|
256
|
+
nextEl = currentIndex === 0 ? radios[radios.length - 1] : radios[currentIndex - 1];
|
|
257
|
+
}
|
|
258
|
+
else if (ev.key === ' ' || ev.key === 'Enter') {
|
|
259
|
+
ev.preventDefault();
|
|
260
|
+
ev.stopPropagation();
|
|
261
|
+
const button = buttonIdMap.get(target.id);
|
|
262
|
+
if (button) {
|
|
263
|
+
this.selectRadioButton(button);
|
|
264
|
+
this.buttonClick(button);
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
// Focus the next radio button
|
|
269
|
+
if (nextEl) {
|
|
270
|
+
const button = buttonIdMap.get(nextEl.id);
|
|
271
|
+
if (button) {
|
|
272
|
+
this.selectRadioButton(button);
|
|
273
|
+
nextEl.focus();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
152
277
|
connectedCallback() {
|
|
153
278
|
prepareOverlay(this.el);
|
|
154
279
|
this.triggerChanged();
|
|
@@ -165,6 +290,8 @@ export class ActionSheet {
|
|
|
165
290
|
if (!((_a = this.htmlAttributes) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
166
291
|
setOverlayId(this.el);
|
|
167
292
|
}
|
|
293
|
+
// Initialize activeRadioId for radio buttons
|
|
294
|
+
this.buttonsChanged();
|
|
168
295
|
}
|
|
169
296
|
componentDidLoad() {
|
|
170
297
|
/**
|
|
@@ -202,19 +329,70 @@ export class ActionSheet {
|
|
|
202
329
|
*/
|
|
203
330
|
this.triggerChanged();
|
|
204
331
|
}
|
|
332
|
+
renderActionSheetButtons(filteredButtons) {
|
|
333
|
+
const mode = getIonMode(this);
|
|
334
|
+
const { activeRadioId } = this;
|
|
335
|
+
return filteredButtons.map((b, index) => {
|
|
336
|
+
var _a;
|
|
337
|
+
const isRadio = ((_a = b.htmlAttributes) === null || _a === void 0 ? void 0 : _a.role) === 'radio';
|
|
338
|
+
const buttonId = this.getButtonId(b, index);
|
|
339
|
+
const radioButtons = this.getRadioButtons();
|
|
340
|
+
const isActiveRadio = isRadio && buttonId === activeRadioId;
|
|
341
|
+
const isFirstRadio = isRadio && b === radioButtons[0];
|
|
342
|
+
// For radio buttons, set tabindex: 0 for the active one, -1 for others
|
|
343
|
+
// For non-radio buttons, use default tabindex (undefined, which means 0)
|
|
344
|
+
/**
|
|
345
|
+
* For radio buttons, set tabindex based on activeRadioId
|
|
346
|
+
* - If the button is the active radio, tabindex is 0
|
|
347
|
+
* - If no radio is active, the first radio button should have tabindex 0
|
|
348
|
+
* - All other radio buttons have tabindex -1
|
|
349
|
+
* For non-radio buttons, use default tabindex (undefined, which means 0)
|
|
350
|
+
*/
|
|
351
|
+
let tabIndex;
|
|
352
|
+
if (isRadio) {
|
|
353
|
+
// Focus on the active radio button
|
|
354
|
+
if (isActiveRadio) {
|
|
355
|
+
tabIndex = 0;
|
|
356
|
+
}
|
|
357
|
+
else if (!activeRadioId && isFirstRadio) {
|
|
358
|
+
// No active radio, first radio gets focus
|
|
359
|
+
tabIndex = 0;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
// All other radios are not focusable
|
|
363
|
+
tabIndex = -1;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
tabIndex = undefined;
|
|
368
|
+
}
|
|
369
|
+
// For radio buttons, set aria-checked based on activeRadioId
|
|
370
|
+
// Otherwise, use the value from htmlAttributes if provided
|
|
371
|
+
const htmlAttrs = Object.assign({}, b.htmlAttributes);
|
|
372
|
+
if (isRadio) {
|
|
373
|
+
htmlAttrs['aria-checked'] = isActiveRadio ? 'true' : 'false';
|
|
374
|
+
}
|
|
375
|
+
return (h("button", Object.assign({}, htmlAttrs, { role: isRadio ? 'radio' : undefined, type: "button", id: buttonId, class: Object.assign(Object.assign({}, buttonClass(b)), { 'action-sheet-selected': isActiveRadio }), onClick: () => {
|
|
376
|
+
if (isRadio) {
|
|
377
|
+
this.selectRadioButton(b);
|
|
378
|
+
}
|
|
379
|
+
this.buttonClick(b);
|
|
380
|
+
}, disabled: b.disabled, tabIndex: tabIndex }), h("span", { class: "action-sheet-button-inner" }, b.icon && h("ion-icon", { icon: b.icon, "aria-hidden": "true", lazy: false, class: "action-sheet-icon" }), b.text), mode === 'md' && h("ion-ripple-effect", null)));
|
|
381
|
+
});
|
|
382
|
+
}
|
|
205
383
|
render() {
|
|
206
|
-
const { header, htmlAttributes, overlayIndex } = this;
|
|
384
|
+
const { header, htmlAttributes, overlayIndex, hasRadioButtons } = this;
|
|
207
385
|
const mode = getIonMode(this);
|
|
208
386
|
const allButtons = this.getButtons();
|
|
209
387
|
const cancelButton = allButtons.find((b) => b.role === 'cancel');
|
|
210
388
|
const buttons = allButtons.filter((b) => b.role !== 'cancel');
|
|
211
389
|
const headerID = `action-sheet-${overlayIndex}-header`;
|
|
212
|
-
return (h(Host, Object.assign({ key: '
|
|
390
|
+
return (h(Host, Object.assign({ key: '173fcff5b1da7c33c267de4667591c946b8c8d03', role: "dialog", "aria-modal": "true", "aria-labelledby": header !== undefined ? headerID : null, tabindex: "-1" }, htmlAttributes, { style: {
|
|
213
391
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
214
|
-
}, class: Object.assign(Object.assign({ [mode]: true }, getClassMap(this.cssClass)), { 'overlay-hidden': true, 'action-sheet-translucent': this.translucent }), onIonActionSheetWillDismiss: this.dispatchCancelHandler, onIonBackdropTap: this.onBackdropTap }), h("ion-backdrop", { key: '
|
|
392
|
+
}, class: Object.assign(Object.assign({ [mode]: true }, getClassMap(this.cssClass)), { 'overlay-hidden': true, 'action-sheet-translucent': this.translucent }), onIonActionSheetWillDismiss: this.dispatchCancelHandler, onIonBackdropTap: this.onBackdropTap }), h("ion-backdrop", { key: '521ede659f747864f6c974e09016436eceb7158c', tappable: this.backdropDismiss }), h("div", { key: '7a7946fc434bc444f16a70638f5e948c69d33fcd', tabindex: "0", "aria-hidden": "true" }), h("div", { key: 'bcff39a580489dbafa255842e57aa8602c6d0f18', class: "action-sheet-wrapper ion-overlay-wrapper", ref: (el) => (this.wrapperEl = el) }, h("div", { key: '84bba13ce14261f0f0daa3f9c77648c9e7f36e0e', class: "action-sheet-container" }, h("div", { key: 'd9c8ac404fd6719a7adf8cb36549f67616f9a0c4', class: "action-sheet-group", ref: (el) => (this.groupEl = el), role: hasRadioButtons ? 'radiogroup' : undefined }, header !== undefined && (h("div", { key: '180433a8ad03ef5c54728a1a8f34715b6921d658', id: headerID, class: {
|
|
215
393
|
'action-sheet-title': true,
|
|
216
394
|
'action-sheet-has-sub-title': this.subHeader !== undefined,
|
|
217
|
-
} }, header, this.subHeader && h("div", { key: '
|
|
395
|
+
} }, header, this.subHeader && h("div", { key: '7138e79e61b1a8f42bc5a9175c57fa2f15d7ec5a', class: "action-sheet-sub-title" }, this.subHeader))), this.renderActionSheetButtons(buttons)), cancelButton && (h("div", { key: 'b617c722f5b8028d73ed34b69310f312c65f34a7', class: "action-sheet-group action-sheet-group-cancel" }, h("button", Object.assign({ key: 'd0dd876fc48815df3710413c201c0b445a8e16c0' }, cancelButton.htmlAttributes, { type: "button", class: buttonClass(cancelButton), onClick: () => this.buttonClick(cancelButton) }), h("span", { key: 'e7b960157cc6fc5fe92a12090b2be55e8ae072e4', class: "action-sheet-button-inner" }, cancelButton.icon && (h("ion-icon", { key: '05498ffc60cab911dbff0ecbc6168dea59ada9a5', icon: cancelButton.icon, "aria-hidden": "true", lazy: false, class: "action-sheet-icon" })), cancelButton.text), mode === 'md' && h("ion-ripple-effect", { key: '3d401346cea301be4ca03671f7370f6f4b0b6bde' })))))), h("div", { key: '971f3c5fcc07f36c28eb469a47ec0290c692e139', tabindex: "0", "aria-hidden": "true" })));
|
|
218
396
|
}
|
|
219
397
|
static get is() { return "ion-action-sheet"; }
|
|
220
398
|
static get encapsulation() { return "scoped"; }
|
|
@@ -568,6 +746,11 @@ export class ActionSheet {
|
|
|
568
746
|
}
|
|
569
747
|
};
|
|
570
748
|
}
|
|
749
|
+
static get states() {
|
|
750
|
+
return {
|
|
751
|
+
"activeRadioId": {}
|
|
752
|
+
};
|
|
753
|
+
}
|
|
571
754
|
static get events() {
|
|
572
755
|
return [{
|
|
573
756
|
"method": "didPresent",
|
|
@@ -822,6 +1005,9 @@ export class ActionSheet {
|
|
|
822
1005
|
static get elementRef() { return "el"; }
|
|
823
1006
|
static get watchers() {
|
|
824
1007
|
return [{
|
|
1008
|
+
"propName": "buttons",
|
|
1009
|
+
"methodName": "buttonsChanged"
|
|
1010
|
+
}, {
|
|
825
1011
|
"propName": "isOpen",
|
|
826
1012
|
"methodName": "onIsOpenChange"
|
|
827
1013
|
}, {
|
|
@@ -829,6 +1015,15 @@ export class ActionSheet {
|
|
|
829
1015
|
"methodName": "triggerChanged"
|
|
830
1016
|
}];
|
|
831
1017
|
}
|
|
1018
|
+
static get listeners() {
|
|
1019
|
+
return [{
|
|
1020
|
+
"name": "keydown",
|
|
1021
|
+
"method": "onKeydown",
|
|
1022
|
+
"target": undefined,
|
|
1023
|
+
"capture": false,
|
|
1024
|
+
"passive": false
|
|
1025
|
+
}];
|
|
1026
|
+
}
|
|
832
1027
|
}
|
|
833
1028
|
const buttonClass = (button) => {
|
|
834
1029
|
return Object.assign({ 'action-sheet-button': true, 'ion-activatable': !button.disabled, 'ion-focusable': !button.disabled, [`action-sheet-${button.role}`]: button.role !== undefined }, getClassMap(button.cssClass));
|
|
@@ -395,13 +395,18 @@ export class Select {
|
|
|
395
395
|
.filter((cls) => cls !== 'hydrated')
|
|
396
396
|
.join(' ');
|
|
397
397
|
const optClass = `${OPTION_CLASS} ${copyClasses}`;
|
|
398
|
+
const isSelected = isOptionSelected(selectValue, value, this.compareWith);
|
|
398
399
|
return {
|
|
399
|
-
role:
|
|
400
|
+
role: isSelected ? 'selected' : '',
|
|
400
401
|
text: option.textContent,
|
|
401
402
|
cssClass: optClass,
|
|
402
403
|
handler: () => {
|
|
403
404
|
this.setValue(value);
|
|
404
405
|
},
|
|
406
|
+
htmlAttributes: {
|
|
407
|
+
'aria-checked': isSelected ? 'true' : 'false',
|
|
408
|
+
role: 'radio',
|
|
409
|
+
},
|
|
405
410
|
};
|
|
406
411
|
});
|
|
407
412
|
// Add "cancel" button
|
|
@@ -828,7 +833,7 @@ export class Select {
|
|
|
828
833
|
* TODO(FW-5592): Remove hasStartEndSlots condition
|
|
829
834
|
*/
|
|
830
835
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
|
|
831
|
-
return (h(Host, { key: '
|
|
836
|
+
return (h(Host, { key: 'd8026835993d0e6dce747098f741a06ae4e4f54d', onClick: this.onClick, class: createColorClasses(this.color, {
|
|
832
837
|
[mode]: true,
|
|
833
838
|
'in-item': inItem,
|
|
834
839
|
'in-item-color': hostContext('ion-item.ion-color', el),
|
|
@@ -846,7 +851,7 @@ export class Select {
|
|
|
846
851
|
[`select-justify-${justify}`]: justifyEnabled,
|
|
847
852
|
[`select-shape-${shape}`]: shape !== undefined,
|
|
848
853
|
[`select-label-placement-${labelPlacement}`]: true,
|
|
849
|
-
}) }, h("label", { key: '
|
|
854
|
+
}) }, h("label", { key: 'fcfb40209d6d07d49c7fdca4884b31abf6ac2567', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'f191664f2290c3890bde1156157c83a6ff17dbe2', class: "select-wrapper-inner" }, h("slot", { key: '317a28d1115b4214f291e228ce0fe6fc782e57d5', name: "start" }), h("div", { key: 'db68e18abd5ca3a1023d7c7b58bf89893ae18073', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: '4274e042267c2234a198b0f65c89477898d08130', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: '2e2eb1ee2b2791e0683d9afb186fde6e938ca59c', class: "select-highlight" })), this.renderBottomContent()));
|
|
850
855
|
}
|
|
851
856
|
static get is() { return "ion-select"; }
|
|
852
857
|
static get encapsulation() { return "shadow"; }
|
package/dist/docs.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"timestamp": "2025-12-
|
|
2
|
+
"timestamp": "2025-12-09T06:08:58",
|
|
3
3
|
"compiler": {
|
|
4
4
|
"name": "@stencil/core",
|
|
5
5
|
"version": "4.38.0",
|
|
@@ -1188,7 +1188,13 @@
|
|
|
1188
1188
|
"docsTags": []
|
|
1189
1189
|
}
|
|
1190
1190
|
],
|
|
1191
|
-
"listeners": [
|
|
1191
|
+
"listeners": [
|
|
1192
|
+
{
|
|
1193
|
+
"event": "keydown",
|
|
1194
|
+
"capture": false,
|
|
1195
|
+
"passive": false
|
|
1196
|
+
}
|
|
1197
|
+
],
|
|
1192
1198
|
"styles": [
|
|
1193
1199
|
{
|
|
1194
1200
|
"name": "--backdrop-opacity",
|
|
@@ -1485,15 +1491,15 @@
|
|
|
1485
1491
|
"ion-select"
|
|
1486
1492
|
],
|
|
1487
1493
|
"dependencies": [
|
|
1488
|
-
"ion-backdrop",
|
|
1489
1494
|
"ion-icon",
|
|
1490
|
-
"ion-ripple-effect"
|
|
1495
|
+
"ion-ripple-effect",
|
|
1496
|
+
"ion-backdrop"
|
|
1491
1497
|
],
|
|
1492
1498
|
"dependencyGraph": {
|
|
1493
1499
|
"ion-action-sheet": [
|
|
1494
|
-
"ion-backdrop",
|
|
1495
1500
|
"ion-icon",
|
|
1496
|
-
"ion-ripple-effect"
|
|
1501
|
+
"ion-ripple-effect",
|
|
1502
|
+
"ion-backdrop"
|
|
1497
1503
|
],
|
|
1498
1504
|
"ion-select": [
|
|
1499
1505
|
"ion-action-sheet"
|
|
@@ -31731,9 +31737,9 @@
|
|
|
31731
31737
|
"ion-backdrop"
|
|
31732
31738
|
],
|
|
31733
31739
|
"ion-action-sheet": [
|
|
31734
|
-
"ion-backdrop",
|
|
31735
31740
|
"ion-icon",
|
|
31736
|
-
"ion-ripple-effect"
|
|
31741
|
+
"ion-ripple-effect",
|
|
31742
|
+
"ion-backdrop"
|
|
31737
31743
|
],
|
|
31738
31744
|
"ion-alert": [
|
|
31739
31745
|
"ion-ripple-effect",
|
|
@@ -119,6 +119,7 @@ const ActionSheet = class {
|
|
|
119
119
|
this.delegateController = createDelegateController(this);
|
|
120
120
|
this.lockController = createLockController();
|
|
121
121
|
this.triggerController = createTriggerController();
|
|
122
|
+
this.hasRadioButtons = false;
|
|
122
123
|
this.presented = false;
|
|
123
124
|
/** @internal */
|
|
124
125
|
this.hasController = false;
|
|
@@ -163,6 +164,19 @@ const ActionSheet = class {
|
|
|
163
164
|
}
|
|
164
165
|
};
|
|
165
166
|
}
|
|
167
|
+
buttonsChanged() {
|
|
168
|
+
const radioButtons = this.getRadioButtons();
|
|
169
|
+
this.hasRadioButtons = radioButtons.length > 0;
|
|
170
|
+
// Initialize activeRadioId when buttons change
|
|
171
|
+
if (this.hasRadioButtons) {
|
|
172
|
+
const checkedButton = radioButtons.find((b) => { var _a; return ((_a = b.htmlAttributes) === null || _a === void 0 ? void 0 : _a['aria-checked']) === 'true'; });
|
|
173
|
+
if (checkedButton) {
|
|
174
|
+
const allButtons = this.getButtons();
|
|
175
|
+
const checkedIndex = allButtons.indexOf(checkedButton);
|
|
176
|
+
this.activeRadioId = this.getButtonId(checkedButton, checkedIndex);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
166
180
|
onIsOpenChange(newValue, oldValue) {
|
|
167
181
|
if (newValue === true && oldValue === false) {
|
|
168
182
|
this.present();
|
|
@@ -243,11 +257,122 @@ const ActionSheet = class {
|
|
|
243
257
|
}
|
|
244
258
|
return true;
|
|
245
259
|
}
|
|
260
|
+
/**
|
|
261
|
+
* Get all buttons regardless of role.
|
|
262
|
+
*/
|
|
246
263
|
getButtons() {
|
|
247
264
|
return this.buttons.map((b) => {
|
|
248
265
|
return typeof b === 'string' ? { text: b } : b;
|
|
249
266
|
});
|
|
250
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Get all radio buttons (buttons with role="radio").
|
|
270
|
+
*/
|
|
271
|
+
getRadioButtons() {
|
|
272
|
+
return this.getButtons().filter((b) => {
|
|
273
|
+
var _a;
|
|
274
|
+
const role = (_a = b.htmlAttributes) === null || _a === void 0 ? void 0 : _a.role;
|
|
275
|
+
return role === 'radio' && !isCancel(role);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Handle radio button selection and update aria-checked state.
|
|
280
|
+
*
|
|
281
|
+
* @param button The radio button that was selected.
|
|
282
|
+
*/
|
|
283
|
+
selectRadioButton(button) {
|
|
284
|
+
const buttonId = this.getButtonId(button);
|
|
285
|
+
// Set the active radio ID (this will trigger a re-render and update aria-checked)
|
|
286
|
+
this.activeRadioId = buttonId;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Get or generate an ID for a button.
|
|
290
|
+
*
|
|
291
|
+
* @param button The button for which to get the ID.
|
|
292
|
+
* @param index Optional index of the button in the buttons array.
|
|
293
|
+
* @returns The ID of the button.
|
|
294
|
+
*/
|
|
295
|
+
getButtonId(button, index) {
|
|
296
|
+
if (button.id) {
|
|
297
|
+
return button.id;
|
|
298
|
+
}
|
|
299
|
+
const allButtons = this.getButtons();
|
|
300
|
+
const buttonIndex = index !== undefined ? index : allButtons.indexOf(button);
|
|
301
|
+
return `action-sheet-button-${this.overlayIndex}-${buttonIndex}`;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* When the action sheet has radio buttons, we want to follow the
|
|
305
|
+
* keyboard navigation pattern for radio groups:
|
|
306
|
+
* - Arrow Down/Right: Move to the next radio button (wrap to first if at end)
|
|
307
|
+
* - Arrow Up/Left: Move to the previous radio button (wrap to last if at start)
|
|
308
|
+
* - Space/Enter: Select the focused radio button and trigger its handler
|
|
309
|
+
*/
|
|
310
|
+
onKeydown(ev) {
|
|
311
|
+
// Only handle keyboard navigation if we have radio buttons
|
|
312
|
+
if (!this.hasRadioButtons || !this.presented) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const target = ev.target;
|
|
316
|
+
// Ignore if the target element is not within the action sheet or not a radio button
|
|
317
|
+
if (!this.el.contains(target) ||
|
|
318
|
+
!target.classList.contains('action-sheet-button') ||
|
|
319
|
+
target.getAttribute('role') !== 'radio') {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
// Get all radio button elements and filter out disabled ones
|
|
323
|
+
const radios = Array.from(this.el.querySelectorAll('.action-sheet-button[role="radio"]')).filter((el) => !el.disabled);
|
|
324
|
+
const currentIndex = radios.findIndex((radio) => radio.id === target.id);
|
|
325
|
+
if (currentIndex === -1) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const allButtons = this.getButtons();
|
|
329
|
+
const radioButtons = this.getRadioButtons();
|
|
330
|
+
/**
|
|
331
|
+
* Build a map of button element IDs to their ActionSheetButton
|
|
332
|
+
* config objects.
|
|
333
|
+
* This allows us to quickly look up which button config corresponds
|
|
334
|
+
* to a DOM element when handling keyboard navigation
|
|
335
|
+
* (e.g., whenuser presses Space/Enter or arrow keys).
|
|
336
|
+
* The key is the ID that was set on the DOM element during render,
|
|
337
|
+
* and the value is the ActionSheetButton config that contains the
|
|
338
|
+
* handler and other properties.
|
|
339
|
+
*/
|
|
340
|
+
const buttonIdMap = new Map();
|
|
341
|
+
radioButtons.forEach((b) => {
|
|
342
|
+
const allIndex = allButtons.indexOf(b);
|
|
343
|
+
const buttonId = this.getButtonId(b, allIndex);
|
|
344
|
+
buttonIdMap.set(buttonId, b);
|
|
345
|
+
});
|
|
346
|
+
let nextEl;
|
|
347
|
+
if (['ArrowDown', 'ArrowRight'].includes(ev.key)) {
|
|
348
|
+
ev.preventDefault();
|
|
349
|
+
ev.stopPropagation();
|
|
350
|
+
nextEl = currentIndex === radios.length - 1 ? radios[0] : radios[currentIndex + 1];
|
|
351
|
+
}
|
|
352
|
+
else if (['ArrowUp', 'ArrowLeft'].includes(ev.key)) {
|
|
353
|
+
ev.preventDefault();
|
|
354
|
+
ev.stopPropagation();
|
|
355
|
+
nextEl = currentIndex === 0 ? radios[radios.length - 1] : radios[currentIndex - 1];
|
|
356
|
+
}
|
|
357
|
+
else if (ev.key === ' ' || ev.key === 'Enter') {
|
|
358
|
+
ev.preventDefault();
|
|
359
|
+
ev.stopPropagation();
|
|
360
|
+
const button = buttonIdMap.get(target.id);
|
|
361
|
+
if (button) {
|
|
362
|
+
this.selectRadioButton(button);
|
|
363
|
+
this.buttonClick(button);
|
|
364
|
+
}
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
// Focus the next radio button
|
|
368
|
+
if (nextEl) {
|
|
369
|
+
const button = buttonIdMap.get(nextEl.id);
|
|
370
|
+
if (button) {
|
|
371
|
+
this.selectRadioButton(button);
|
|
372
|
+
nextEl.focus();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
251
376
|
connectedCallback() {
|
|
252
377
|
prepareOverlay(this.el);
|
|
253
378
|
this.triggerChanged();
|
|
@@ -264,6 +389,8 @@ const ActionSheet = class {
|
|
|
264
389
|
if (!((_a = this.htmlAttributes) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
265
390
|
setOverlayId(this.el);
|
|
266
391
|
}
|
|
392
|
+
// Initialize activeRadioId for radio buttons
|
|
393
|
+
this.buttonsChanged();
|
|
267
394
|
}
|
|
268
395
|
componentDidLoad() {
|
|
269
396
|
/**
|
|
@@ -301,22 +428,74 @@ const ActionSheet = class {
|
|
|
301
428
|
*/
|
|
302
429
|
this.triggerChanged();
|
|
303
430
|
}
|
|
431
|
+
renderActionSheetButtons(filteredButtons) {
|
|
432
|
+
const mode = getIonMode(this);
|
|
433
|
+
const { activeRadioId } = this;
|
|
434
|
+
return filteredButtons.map((b, index) => {
|
|
435
|
+
var _a;
|
|
436
|
+
const isRadio = ((_a = b.htmlAttributes) === null || _a === void 0 ? void 0 : _a.role) === 'radio';
|
|
437
|
+
const buttonId = this.getButtonId(b, index);
|
|
438
|
+
const radioButtons = this.getRadioButtons();
|
|
439
|
+
const isActiveRadio = isRadio && buttonId === activeRadioId;
|
|
440
|
+
const isFirstRadio = isRadio && b === radioButtons[0];
|
|
441
|
+
// For radio buttons, set tabindex: 0 for the active one, -1 for others
|
|
442
|
+
// For non-radio buttons, use default tabindex (undefined, which means 0)
|
|
443
|
+
/**
|
|
444
|
+
* For radio buttons, set tabindex based on activeRadioId
|
|
445
|
+
* - If the button is the active radio, tabindex is 0
|
|
446
|
+
* - If no radio is active, the first radio button should have tabindex 0
|
|
447
|
+
* - All other radio buttons have tabindex -1
|
|
448
|
+
* For non-radio buttons, use default tabindex (undefined, which means 0)
|
|
449
|
+
*/
|
|
450
|
+
let tabIndex;
|
|
451
|
+
if (isRadio) {
|
|
452
|
+
// Focus on the active radio button
|
|
453
|
+
if (isActiveRadio) {
|
|
454
|
+
tabIndex = 0;
|
|
455
|
+
}
|
|
456
|
+
else if (!activeRadioId && isFirstRadio) {
|
|
457
|
+
// No active radio, first radio gets focus
|
|
458
|
+
tabIndex = 0;
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
// All other radios are not focusable
|
|
462
|
+
tabIndex = -1;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
tabIndex = undefined;
|
|
467
|
+
}
|
|
468
|
+
// For radio buttons, set aria-checked based on activeRadioId
|
|
469
|
+
// Otherwise, use the value from htmlAttributes if provided
|
|
470
|
+
const htmlAttrs = Object.assign({}, b.htmlAttributes);
|
|
471
|
+
if (isRadio) {
|
|
472
|
+
htmlAttrs['aria-checked'] = isActiveRadio ? 'true' : 'false';
|
|
473
|
+
}
|
|
474
|
+
return (h("button", Object.assign({}, htmlAttrs, { role: isRadio ? 'radio' : undefined, type: "button", id: buttonId, class: Object.assign(Object.assign({}, buttonClass(b)), { 'action-sheet-selected': isActiveRadio }), onClick: () => {
|
|
475
|
+
if (isRadio) {
|
|
476
|
+
this.selectRadioButton(b);
|
|
477
|
+
}
|
|
478
|
+
this.buttonClick(b);
|
|
479
|
+
}, disabled: b.disabled, tabIndex: tabIndex }), h("span", { class: "action-sheet-button-inner" }, b.icon && h("ion-icon", { icon: b.icon, "aria-hidden": "true", lazy: false, class: "action-sheet-icon" }), b.text), mode === 'md' && h("ion-ripple-effect", null)));
|
|
480
|
+
});
|
|
481
|
+
}
|
|
304
482
|
render() {
|
|
305
|
-
const { header, htmlAttributes, overlayIndex } = this;
|
|
483
|
+
const { header, htmlAttributes, overlayIndex, hasRadioButtons } = this;
|
|
306
484
|
const mode = getIonMode(this);
|
|
307
485
|
const allButtons = this.getButtons();
|
|
308
486
|
const cancelButton = allButtons.find((b) => b.role === 'cancel');
|
|
309
487
|
const buttons = allButtons.filter((b) => b.role !== 'cancel');
|
|
310
488
|
const headerID = `action-sheet-${overlayIndex}-header`;
|
|
311
|
-
return (h(Host, Object.assign({ key: '
|
|
489
|
+
return (h(Host, Object.assign({ key: '173fcff5b1da7c33c267de4667591c946b8c8d03', role: "dialog", "aria-modal": "true", "aria-labelledby": header !== undefined ? headerID : null, tabindex: "-1" }, htmlAttributes, { style: {
|
|
312
490
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
313
|
-
}, class: Object.assign(Object.assign({ [mode]: true }, getClassMap(this.cssClass)), { 'overlay-hidden': true, 'action-sheet-translucent': this.translucent }), onIonActionSheetWillDismiss: this.dispatchCancelHandler, onIonBackdropTap: this.onBackdropTap }), h("ion-backdrop", { key: '
|
|
491
|
+
}, class: Object.assign(Object.assign({ [mode]: true }, getClassMap(this.cssClass)), { 'overlay-hidden': true, 'action-sheet-translucent': this.translucent }), onIonActionSheetWillDismiss: this.dispatchCancelHandler, onIonBackdropTap: this.onBackdropTap }), h("ion-backdrop", { key: '521ede659f747864f6c974e09016436eceb7158c', tappable: this.backdropDismiss }), h("div", { key: '7a7946fc434bc444f16a70638f5e948c69d33fcd', tabindex: "0", "aria-hidden": "true" }), h("div", { key: 'bcff39a580489dbafa255842e57aa8602c6d0f18', class: "action-sheet-wrapper ion-overlay-wrapper", ref: (el) => (this.wrapperEl = el) }, h("div", { key: '84bba13ce14261f0f0daa3f9c77648c9e7f36e0e', class: "action-sheet-container" }, h("div", { key: 'd9c8ac404fd6719a7adf8cb36549f67616f9a0c4', class: "action-sheet-group", ref: (el) => (this.groupEl = el), role: hasRadioButtons ? 'radiogroup' : undefined }, header !== undefined && (h("div", { key: '180433a8ad03ef5c54728a1a8f34715b6921d658', id: headerID, class: {
|
|
314
492
|
'action-sheet-title': true,
|
|
315
493
|
'action-sheet-has-sub-title': this.subHeader !== undefined,
|
|
316
|
-
} }, header, this.subHeader && h("div", { key: '
|
|
494
|
+
} }, header, this.subHeader && h("div", { key: '7138e79e61b1a8f42bc5a9175c57fa2f15d7ec5a', class: "action-sheet-sub-title" }, this.subHeader))), this.renderActionSheetButtons(buttons)), cancelButton && (h("div", { key: 'b617c722f5b8028d73ed34b69310f312c65f34a7', class: "action-sheet-group action-sheet-group-cancel" }, h("button", Object.assign({ key: 'd0dd876fc48815df3710413c201c0b445a8e16c0' }, cancelButton.htmlAttributes, { type: "button", class: buttonClass(cancelButton), onClick: () => this.buttonClick(cancelButton) }), h("span", { key: 'e7b960157cc6fc5fe92a12090b2be55e8ae072e4', class: "action-sheet-button-inner" }, cancelButton.icon && (h("ion-icon", { key: '05498ffc60cab911dbff0ecbc6168dea59ada9a5', icon: cancelButton.icon, "aria-hidden": "true", lazy: false, class: "action-sheet-icon" })), cancelButton.text), mode === 'md' && h("ion-ripple-effect", { key: '3d401346cea301be4ca03671f7370f6f4b0b6bde' })))))), h("div", { key: '971f3c5fcc07f36c28eb469a47ec0290c692e139', tabindex: "0", "aria-hidden": "true" })));
|
|
317
495
|
}
|
|
318
496
|
get el() { return getElement(this); }
|
|
319
497
|
static get watchers() { return {
|
|
498
|
+
"buttons": ["buttonsChanged"],
|
|
320
499
|
"isOpen": ["onIsOpenChange"],
|
|
321
500
|
"trigger": ["triggerChanged"]
|
|
322
501
|
}; }
|
|
@@ -395,13 +395,18 @@ const Select = class {
|
|
|
395
395
|
.filter((cls) => cls !== 'hydrated')
|
|
396
396
|
.join(' ');
|
|
397
397
|
const optClass = `${OPTION_CLASS} ${copyClasses}`;
|
|
398
|
+
const isSelected = isOptionSelected(selectValue, value, this.compareWith);
|
|
398
399
|
return {
|
|
399
|
-
role:
|
|
400
|
+
role: isSelected ? 'selected' : '',
|
|
400
401
|
text: option.textContent,
|
|
401
402
|
cssClass: optClass,
|
|
402
403
|
handler: () => {
|
|
403
404
|
this.setValue(value);
|
|
404
405
|
},
|
|
406
|
+
htmlAttributes: {
|
|
407
|
+
'aria-checked': isSelected ? 'true' : 'false',
|
|
408
|
+
role: 'radio',
|
|
409
|
+
},
|
|
405
410
|
};
|
|
406
411
|
});
|
|
407
412
|
// Add "cancel" button
|
|
@@ -782,7 +787,7 @@ const Select = class {
|
|
|
782
787
|
* TODO(FW-5592): Remove hasStartEndSlots condition
|
|
783
788
|
*/
|
|
784
789
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
|
|
785
|
-
return (h(Host, { key: '
|
|
790
|
+
return (h(Host, { key: 'd8026835993d0e6dce747098f741a06ae4e4f54d', onClick: this.onClick, class: createColorClasses(this.color, {
|
|
786
791
|
[mode]: true,
|
|
787
792
|
'in-item': inItem,
|
|
788
793
|
'in-item-color': hostContext('ion-item.ion-color', el),
|
|
@@ -800,7 +805,7 @@ const Select = class {
|
|
|
800
805
|
[`select-justify-${justify}`]: justifyEnabled,
|
|
801
806
|
[`select-shape-${shape}`]: shape !== undefined,
|
|
802
807
|
[`select-label-placement-${labelPlacement}`]: true,
|
|
803
|
-
}) }, h("label", { key: '
|
|
808
|
+
}) }, h("label", { key: 'fcfb40209d6d07d49c7fdca4884b31abf6ac2567', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick }, this.renderLabelContainer(), h("div", { key: 'f191664f2290c3890bde1156157c83a6ff17dbe2', class: "select-wrapper-inner" }, h("slot", { key: '317a28d1115b4214f291e228ce0fe6fc782e57d5', name: "start" }), h("div", { key: 'db68e18abd5ca3a1023d7c7b58bf89893ae18073', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: '4274e042267c2234a198b0f65c89477898d08130', name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: '2e2eb1ee2b2791e0683d9afb186fde6e938ca59c', class: "select-highlight" })), this.renderBottomContent()));
|
|
804
809
|
}
|
|
805
810
|
get el() { return getElement(this); }
|
|
806
811
|
static get watchers() { return {
|