@ionic/core 9.0.2-dev.11788201761.1a20dc3a → 9.0.2-dev.11789078072.1cdaafd9
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/ion-input.js +1 -1
- package/components/ion-select.js +1 -1
- package/components/ion-textarea.js +1 -1
- package/components/p-CobW5muY.js +4 -0
- package/dist/cjs/ion-input.cjs.entry.js +6 -9
- package/dist/cjs/ion-select_3.cjs.entry.js +57 -37
- package/dist/cjs/ion-textarea.cjs.entry.js +5 -8
- package/dist/cjs/ionic.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{slot-mutation-controller-D6IjSEIh.js → slot-mutation-controller-yNStVQqX.js} +69 -0
- package/dist/collection/components/input/input.js +6 -9
- package/dist/collection/components/select/select.js +67 -38
- package/dist/collection/components/textarea/textarea.js +5 -8
- package/dist/collection/utils/forms/click-controller.js +70 -0
- package/dist/collection/utils/forms/index.js +1 -0
- package/dist/docs.json +8 -2
- package/dist/esm/ion-input.entry.js +6 -9
- package/dist/esm/ion-select_3.entry.js +57 -37
- package/dist/esm/ion-textarea.entry.js +5 -8
- package/dist/esm/ionic.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{slot-mutation-controller-B5NpUZ-N.js → slot-mutation-controller-BVnANfIt.js} +68 -1
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-39b6b6ba.entry.js +4 -0
- package/dist/ionic/p-4d7779c8.entry.js +4 -0
- package/dist/ionic/p-91407d16.entry.js +4 -0
- package/dist/ionic/p-DcCEP7Ss.js +4 -0
- package/dist/types/components/input/input.d.ts +1 -0
- package/dist/types/components/select/select.d.ts +11 -0
- package/dist/types/components/textarea/textarea.d.ts +1 -0
- package/dist/types/utils/forms/click-controller.d.ts +36 -0
- package/dist/types/utils/forms/index.d.ts +1 -0
- package/hydrate/index.js +133 -52
- package/hydrate/index.mjs +133 -52
- package/package.json +1 -1
- package/components/p-Cw6WocLJ.js +0 -4
- package/dist/ionic/p-7e394cc3.entry.js +0 -4
- package/dist/ionic/p-CkxVYPtX.js +0 -4
- package/dist/ionic/p-a20a2ebe.entry.js +0 -4
- package/dist/ionic/p-f83b3e0c.entry.js +0 -4
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
|
-
import { Build, Host, h, forceUpdate } from "@stencil/core";
|
|
4
|
+
import { Build, Host, h, forceUpdate, } from "@stencil/core";
|
|
5
5
|
import { ENABLE_HTML_CONTENT_DEFAULT } from "../../utils/config";
|
|
6
|
-
import { compareOptions, createNotchController, createStartContainerController, isOptionSelected, checkInvalidState, } from "../../utils/forms/index";
|
|
6
|
+
import { compareOptions, createClickController, createNotchController, createStartContainerController, getSlottedClickContent, isOptionSelected, checkInvalidState, } from "../../utils/forms/index";
|
|
7
7
|
import { focusVisibleElement, renderHiddenInput, inheritAttributes } from "../../utils/helpers";
|
|
8
8
|
import { printIonWarning } from "../../utils/logging/index";
|
|
9
9
|
import { actionSheetController, alertController, popoverController, modalController } from "../../utils/overlays";
|
|
@@ -109,41 +109,21 @@ export class Select {
|
|
|
109
109
|
*/
|
|
110
110
|
this.required = false;
|
|
111
111
|
this.onClick = (ev) => {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
*
|
|
125
|
-
* Clicking a slotted element also causes a click
|
|
126
|
-
* on the <label> element (since it wraps the slots).
|
|
127
|
-
* Clicking <label> dispatches another click event on
|
|
128
|
-
* the native form control that then bubbles up to this
|
|
129
|
-
* listener. This additional event targets the host
|
|
130
|
-
* element, so the select overlay is opened.
|
|
131
|
-
*
|
|
132
|
-
* When the slotted elements are clicked (and therefore
|
|
133
|
-
* the ancestor <label> element) we want to prevent the label
|
|
134
|
-
* from dispatching another click event.
|
|
135
|
-
*
|
|
136
|
-
* Do not call stopPropagation() because this will cause
|
|
137
|
-
* click handlers on the slotted elements to never fire in React.
|
|
138
|
-
* When developers do onClick in React a native "click" listener
|
|
139
|
-
* is added on the root element, not the slotted element. When that
|
|
140
|
-
* native click listener fires, React then dispatches the synthetic
|
|
141
|
-
* click event on the slotted element. However, if stopPropagation
|
|
142
|
-
* is called then the native click event will never bubble up
|
|
143
|
-
* to the root element.
|
|
144
|
-
*/
|
|
145
|
-
ev.preventDefault();
|
|
112
|
+
const slotted = getSlottedClickContent(ev, this.el);
|
|
113
|
+
/**
|
|
114
|
+
* Interactive slotted content, such as a button or a checkbox, handles its
|
|
115
|
+
* own click, so it should not open the select as well. Any other slotted
|
|
116
|
+
* content is decorative and behaves the same as clicking the select
|
|
117
|
+
* itself.
|
|
118
|
+
*/
|
|
119
|
+
if (slotted !== null) {
|
|
120
|
+
const interactive = ev.target.closest(INTERACTIVE_SLOTTED_CONTENT);
|
|
121
|
+
if (interactive !== null && slotted.contains(interactive)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
146
124
|
}
|
|
125
|
+
this.setFocus();
|
|
126
|
+
this.open(ev);
|
|
147
127
|
};
|
|
148
128
|
this.onFocus = () => {
|
|
149
129
|
this.hasFocus = true;
|
|
@@ -214,6 +194,7 @@ export class Select {
|
|
|
214
194
|
return Build.isBrowser && getIonMode(this) === 'md' && this.fill === 'outline';
|
|
215
195
|
});
|
|
216
196
|
this.startContainerController.calculateStartContainerWidth();
|
|
197
|
+
this.clickController = createClickController(el);
|
|
217
198
|
this.updateOverlayOptions();
|
|
218
199
|
this.emitStyle();
|
|
219
200
|
this.mutationO = watchForOptions(this.el, 'ion-select-option', async () => {
|
|
@@ -750,6 +731,18 @@ export class Select {
|
|
|
750
731
|
};
|
|
751
732
|
this.ionStyle.emit(style);
|
|
752
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* The label wrapping the slots has no `for` attribute, so the browser
|
|
736
|
+
* forwards a click on slotted content to the label's first labelable
|
|
737
|
+
* descendant, the internal button. That forwarded click bubbles back out of
|
|
738
|
+
* the shadow root targeting the host, where it would be emitted a second
|
|
739
|
+
* time and open the select. The controller swallows it during the capture
|
|
740
|
+
* phase, leaving the click on the slotted content itself alone so slotted
|
|
741
|
+
* links, checkboxes and buttons keep their default behavior.
|
|
742
|
+
*/
|
|
743
|
+
onClickCapture(ev) {
|
|
744
|
+
this.clickController?.handleClickCapture(ev);
|
|
745
|
+
}
|
|
753
746
|
renderLabel() {
|
|
754
747
|
const { label } = this;
|
|
755
748
|
return (h("div", { class: {
|
|
@@ -947,7 +940,7 @@ export class Select {
|
|
|
947
940
|
const hasOutlineFill = mode === 'md' && fill === 'outline';
|
|
948
941
|
renderHiddenInput(true, el, name, parseValue(value), disabled);
|
|
949
942
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || isExpanded));
|
|
950
|
-
return (h(Host, { key: '
|
|
943
|
+
return (h(Host, { key: '31585557ac49c439939b5a084867f5039dc2f1ce', onClick: this.onClick, class: createColorClasses(this.color, {
|
|
951
944
|
[mode]: true,
|
|
952
945
|
'in-item': inItem,
|
|
953
946
|
'in-item-color': hostContext('ion-item.ion-color', el),
|
|
@@ -965,7 +958,7 @@ export class Select {
|
|
|
965
958
|
[`select-justify-${justify}`]: justifyEnabled,
|
|
966
959
|
[`select-shape-${shape}`]: shape !== undefined,
|
|
967
960
|
[`select-label-placement-${labelPlacement}`]: true,
|
|
968
|
-
}) }, h("label", { key: '
|
|
961
|
+
}) }, h("label", { key: '368f5f0c77550be6a743752dd1acbfe9915f0a3d', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick, part: "wrapper" }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '2e9039eaa15f398ebe54808ff659db89d3621681', class: "select-start", part: "start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: 'b83e362f89bd8cbc564ad7122778e3a0f08be66d', name: "start" })), h("div", { key: 'f55ac9fc3bbd9a4a3819a101a3780fc02ae8a8e0', class: "select-control", part: "control" }, this.renderLabel(), h("div", { key: 'e32984e57cda5cf341af8e354a2aef5a114827c0', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox(), !hasFloatingOrStackedLabel && this.renderSelectIcon())), h("div", { key: 'fc4cb7c392c46129afe709d9557ad9a10d72b564', class: "select-end", part: "end" }, hasFloatingOrStackedLabel && this.renderSelectIcon(), h("slot", { key: '6d65c92cdafe985a769fb3b41a58625077803eef', name: "end" })), shouldRenderHighlight && h("div", { key: '9a912c64d7cd77f074a069d126cd0bada1b71eed', class: "select-highlight" })), this.renderBottomContent()));
|
|
969
962
|
}
|
|
970
963
|
static get is() { return "ion-select"; }
|
|
971
964
|
static get encapsulation() { return "shadow"; }
|
|
@@ -1611,6 +1604,15 @@ export class Select {
|
|
|
1611
1604
|
"methodName": "styleChanged"
|
|
1612
1605
|
}];
|
|
1613
1606
|
}
|
|
1607
|
+
static get listeners() {
|
|
1608
|
+
return [{
|
|
1609
|
+
"name": "click",
|
|
1610
|
+
"method": "onClickCapture",
|
|
1611
|
+
"target": undefined,
|
|
1612
|
+
"capture": true,
|
|
1613
|
+
"passive": false
|
|
1614
|
+
}];
|
|
1615
|
+
}
|
|
1614
1616
|
}
|
|
1615
1617
|
const getOptionValue = (el) => {
|
|
1616
1618
|
const value = el.value;
|
|
@@ -1851,3 +1853,30 @@ const extractOptionContent = (option, customHTMLEnabled) => {
|
|
|
1851
1853
|
};
|
|
1852
1854
|
let selectIds = 0;
|
|
1853
1855
|
const OPTION_CLASS = 'select-interface-option';
|
|
1856
|
+
/**
|
|
1857
|
+
* Slotted content that handles its own click, so clicking it should not also
|
|
1858
|
+
* open the select.
|
|
1859
|
+
*
|
|
1860
|
+
* This deliberately does not reuse `focusableQueryString`. That selector
|
|
1861
|
+
* answers whether an element can take focus right now, which is a different
|
|
1862
|
+
* question: an ion-radio outside a radio group carries tabindex="-1" from the
|
|
1863
|
+
* group's roving tabindex, and disabled controls are excluded, yet both still
|
|
1864
|
+
* handle their own clicks.
|
|
1865
|
+
*/
|
|
1866
|
+
const INTERACTIVE_SLOTTED_CONTENT = [
|
|
1867
|
+
'a[href]',
|
|
1868
|
+
'button',
|
|
1869
|
+
'input[type="checkbox"]',
|
|
1870
|
+
'input[type="radio"]',
|
|
1871
|
+
'ion-button',
|
|
1872
|
+
'ion-checkbox',
|
|
1873
|
+
'ion-radio',
|
|
1874
|
+
'ion-toggle',
|
|
1875
|
+
'[tabindex]:not([tabindex^="-"])',
|
|
1876
|
+
/**
|
|
1877
|
+
* Covers the remaining Ionic controls. The tags above are still listed
|
|
1878
|
+
* because ion-checkbox and ion-radio only carry this class when they are
|
|
1879
|
+
* outside an item, so a select inside an item would lose the match.
|
|
1880
|
+
*/
|
|
1881
|
+
'.ion-focusable',
|
|
1882
|
+
].join(', ');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
4
|
import { Build, Host, forceUpdate, h, writeTask, } from "@stencil/core";
|
|
5
|
-
import { createNotchController, createStartContainerController, checkInvalidState } from "../../utils/forms/index";
|
|
5
|
+
import { createClickController, createNotchController, createStartContainerController, checkInvalidState, } from "../../utils/forms/index";
|
|
6
6
|
import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from "../../utils/helpers";
|
|
7
7
|
import { createSlotMutationController } from "../../utils/slot-mutation-controller";
|
|
8
8
|
import { createColorClasses, hostContext } from "../../utils/theme";
|
|
@@ -189,11 +189,7 @@ export class Textarea {
|
|
|
189
189
|
* Instead, the click event from the ion-textarea is emitted.
|
|
190
190
|
*/
|
|
191
191
|
onClickCapture(ev) {
|
|
192
|
-
|
|
193
|
-
if (nativeInput && ev.target === nativeInput) {
|
|
194
|
-
ev.stopPropagation();
|
|
195
|
-
this.el.click();
|
|
196
|
-
}
|
|
192
|
+
this.clickController?.handleClickCapture(ev);
|
|
197
193
|
}
|
|
198
194
|
connectedCallback() {
|
|
199
195
|
const { el } = this;
|
|
@@ -206,6 +202,7 @@ export class Textarea {
|
|
|
206
202
|
return Build.isBrowser && getIonMode(this) === 'md' && this.fill === 'outline';
|
|
207
203
|
});
|
|
208
204
|
this.startContainerController.calculateStartContainerWidth();
|
|
205
|
+
this.clickController = createClickController(el, () => this.nativeInput);
|
|
209
206
|
// Watch for class changes to update validation state
|
|
210
207
|
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
|
|
211
208
|
this.validationObserver = new MutationObserver(() => {
|
|
@@ -461,7 +458,7 @@ export class Textarea {
|
|
|
461
458
|
* the textarea has a value or is focused.
|
|
462
459
|
*/
|
|
463
460
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus));
|
|
464
|
-
return (h(Host, { key: '
|
|
461
|
+
return (h(Host, { key: '0f01ed6565313c45beaea5e0aa429a86b9cfa1a0', class: createColorClasses(this.color, {
|
|
465
462
|
[mode]: true,
|
|
466
463
|
'has-value': hasValue,
|
|
467
464
|
'has-focus': hasFocus,
|
|
@@ -470,7 +467,7 @@ export class Textarea {
|
|
|
470
467
|
[`textarea-shape-${shape}`]: shape !== undefined,
|
|
471
468
|
[`textarea-label-placement-${labelPlacement}`]: true,
|
|
472
469
|
'textarea-disabled': disabled,
|
|
473
|
-
}) }, h("label", { key: '
|
|
470
|
+
}) }, h("label", { key: 'a59548c3d3bf66543fd6e06b5c3b6e649f963164', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '29055193d664d9fe84ba0340fdcbbc54fec0e403', class: "textarea-start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: '2cc122d9541ce7ae3c2309c27123b6c6c91cbfc7', name: "start" })), h("div", { key: 'c847a6fa1eaf6ef2b296415cd72bdd3aee9828a2', class: "textarea-control" }, this.renderLabel(), h("div", { key: 'f9205f82d6346daa86dc7e0f6e1eb221c76ed7b0', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", { key: '0bdb517fa5b3a44eeb3bb2ed1f9a8eab21a9a6a1', class: "native-textarea", ref: (el) => (this.nativeInput = el), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, minLength: this.minlength, maxLength: this.maxlength, name: this.name, placeholder: this.placeholder || '', readOnly: this.readonly, required: this.required, spellcheck: this.spellcheck, cols: this.cols, rows: this.rows, wrap: this.wrap, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined, ...this.inheritedAttributes }, value))), h("div", { key: '1ce661b800ef12146bdb97fb193e22be227ea052', class: "textarea-end" }, h("slot", { key: 'aca5704db48fe8e0e784cf57f2978ae2d63b3edc', name: "end" })), shouldRenderHighlight && h("div", { key: 'd916816d44cbdb4bc9a05032c5490eaa9bf3ba47', class: "textarea-highlight" })), this.renderBottomContent()));
|
|
474
471
|
}
|
|
475
472
|
static get is() { return "ion-textarea"; }
|
|
476
473
|
static get encapsulation() { return "scoped"; }
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
|
+
*/
|
|
4
|
+
import { raf } from "../helpers";
|
|
5
|
+
/**
|
|
6
|
+
* The content slotted into a form control's start or end slot that a click
|
|
7
|
+
* started on, or `null` when the click did not start on slotted content.
|
|
8
|
+
*
|
|
9
|
+
* The slotted element is compared against the host in case the form control
|
|
10
|
+
* itself is slotted into, for example, an item. Without that check a control
|
|
11
|
+
* carrying slot="start"/"end" would treat every click on itself as a slotted
|
|
12
|
+
* click.
|
|
13
|
+
*/
|
|
14
|
+
export const getSlottedClickContent = (ev, el) => {
|
|
15
|
+
const slotted = ev.target.closest('[slot="start"], [slot="end"]');
|
|
16
|
+
return slotted !== null && slotted !== el && el.contains(slotted) ? slotted : null;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Whether a click started on content slotted into a form control's start or
|
|
20
|
+
* end slot.
|
|
21
|
+
*/
|
|
22
|
+
const isSlottedClick = (ev, el) => getSlottedClickContent(ev, el) !== null;
|
|
23
|
+
/**
|
|
24
|
+
* A utility for form components that wrap their content in a <label>, such as
|
|
25
|
+
* ion-input, ion-textarea and ion-select.
|
|
26
|
+
*
|
|
27
|
+
* Clicking slotted content also clicks that label, and the browser follows it
|
|
28
|
+
* with a click on the label's control. That second click would be emitted from
|
|
29
|
+
* the host as a duplicate, so the slotted click is remembered and the click
|
|
30
|
+
* that follows it is suppressed.
|
|
31
|
+
*
|
|
32
|
+
* Browsers skip the forwarding when the click lands on interactive content,
|
|
33
|
+
* such as a slotted button, so the slotted click is remembered for a frame
|
|
34
|
+
* rather than until a forwarded click that may never arrive.
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
* @param el - The host element.
|
|
38
|
+
* @param getNativeInput - A callback returning the native form control the
|
|
39
|
+
* label points at, for components that have one. Those components emit the
|
|
40
|
+
* click from the host rather than the control, so the control's click is
|
|
41
|
+
* always stopped and re-dispatched from the host. Omit it for components whose
|
|
42
|
+
* label has no `for` attribute, such as ion-select, where the browser forwards
|
|
43
|
+
* to an internal control that already re-bubbles targeting the host.
|
|
44
|
+
*/
|
|
45
|
+
export const createClickController = (el, getNativeInput) => {
|
|
46
|
+
let hasSlottedClick = false;
|
|
47
|
+
const handleClickCapture = (ev) => {
|
|
48
|
+
if (isSlottedClick(ev, el)) {
|
|
49
|
+
hasSlottedClick = true;
|
|
50
|
+
raf(() => (hasSlottedClick = false));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (getNativeInput === undefined) {
|
|
54
|
+
if (hasSlottedClick) {
|
|
55
|
+
ev.stopPropagation();
|
|
56
|
+
hasSlottedClick = false;
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const nativeInput = getNativeInput();
|
|
61
|
+
if (nativeInput !== undefined && ev.target === nativeInput) {
|
|
62
|
+
ev.stopPropagation();
|
|
63
|
+
if (!hasSlottedClick) {
|
|
64
|
+
el.click();
|
|
65
|
+
}
|
|
66
|
+
hasSlottedClick = false;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
return { handleClickCapture };
|
|
70
|
+
};
|
package/dist/docs.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"timestamp": "2026-
|
|
2
|
+
"timestamp": "2026-09-10T22:09:45",
|
|
3
3
|
"compiler": {
|
|
4
4
|
"name": "@stencil/core",
|
|
5
5
|
"version": "4.44.2",
|
|
@@ -31461,7 +31461,13 @@
|
|
|
31461
31461
|
"docsTags": []
|
|
31462
31462
|
}
|
|
31463
31463
|
],
|
|
31464
|
-
"listeners": [
|
|
31464
|
+
"listeners": [
|
|
31465
|
+
{
|
|
31466
|
+
"event": "click",
|
|
31467
|
+
"capture": true,
|
|
31468
|
+
"passive": false
|
|
31469
|
+
}
|
|
31470
|
+
],
|
|
31465
31471
|
"styles": [
|
|
31466
31472
|
{
|
|
31467
31473
|
"name": "--background",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
4
|
import { r as registerInstance, c as createEvent, i as forceUpdate, h, d as Host, g as getElement } from './index-C23AVPx9.js';
|
|
5
|
-
import { c as createSlotMutationController, a as createNotchController, b as createStartContainerController } from './slot-mutation-controller-
|
|
5
|
+
import { c as createSlotMutationController, a as createNotchController, b as createStartContainerController, d as createClickController } from './slot-mutation-controller-BVnANfIt.js';
|
|
6
6
|
import { c as checkInvalidState } from './validity-DJztqcrH.js';
|
|
7
7
|
import { d as debounceEvent, b as inheritAttributes, i as inheritAriaAttributes, c as componentOnReady } from './helpers-BSmc0nst.js';
|
|
8
8
|
import { h as hostContext, c as createColorClasses } from './theme-byZM6qHV.js';
|
|
@@ -232,11 +232,7 @@ const Input = class {
|
|
|
232
232
|
* Instead, the click event from the ion-input is emitted.
|
|
233
233
|
*/
|
|
234
234
|
onClickCapture(ev) {
|
|
235
|
-
|
|
236
|
-
if (nativeInput && ev.target === nativeInput) {
|
|
237
|
-
ev.stopPropagation();
|
|
238
|
-
this.el.click();
|
|
239
|
-
}
|
|
235
|
+
this.clickController?.handleClickCapture(ev);
|
|
240
236
|
}
|
|
241
237
|
componentWillLoad() {
|
|
242
238
|
this.inheritedAttributes = {
|
|
@@ -257,6 +253,7 @@ const Input = class {
|
|
|
257
253
|
return getIonMode(this) === 'md' && this.fill === 'outline';
|
|
258
254
|
});
|
|
259
255
|
this.startContainerController.calculateStartContainerWidth();
|
|
256
|
+
this.clickController = createClickController(el, () => this.nativeInput);
|
|
260
257
|
// Watch for class changes to update validation state
|
|
261
258
|
if (typeof MutationObserver !== 'undefined') {
|
|
262
259
|
this.validationObserver = new MutationObserver(() => {
|
|
@@ -532,7 +529,7 @@ const Input = class {
|
|
|
532
529
|
* the input has a value or is focused.
|
|
533
530
|
*/
|
|
534
531
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus));
|
|
535
|
-
return (h(Host, { key: '
|
|
532
|
+
return (h(Host, { key: '290cf987de670276bf40b9f19d35d3769d9c0540', class: createColorClasses(this.color, {
|
|
536
533
|
[mode]: true,
|
|
537
534
|
'has-value': hasValue,
|
|
538
535
|
'has-focus': hasFocus,
|
|
@@ -543,14 +540,14 @@ const Input = class {
|
|
|
543
540
|
'in-item': inItem,
|
|
544
541
|
'in-item-color': hostContext('ion-item.ion-color', this.el),
|
|
545
542
|
'input-disabled': disabled,
|
|
546
|
-
}) }, h("label", { key: '
|
|
543
|
+
}) }, h("label", { key: '27f4bd671f6084a7e608e4f9442192c950879dc2', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '7827c61ffdd02952fc8cf2096f621b50fc51ec03', class: "input-start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: '1d8af52d3506742d87334b388859a8bf613e1be0', name: "start" })), h("div", { key: '927a412064538c00df98fabc06a32c564d05e984', class: "input-control" }, this.renderLabel(), h("div", { key: '05f80c0b11a7da57445f976bd2dbb557fa63c9bc', class: "native-wrapper", onClick: this.onLabelClick }, h("input", { key: 'cb6f5d81ba7c2a04cc4c9deb5d3f5bca8562b6db', class: "native-input", ref: (input) => (this.nativeInput = input), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoComplete: this.autocomplete, autoCorrect: this.autocorrect ? 'on' : 'off', autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, min: this.min, max: this.max, minLength: this.minlength, maxLength: this.maxlength, multiple: this.multiple, name: this.name, pattern: this.pattern, placeholder: this.placeholder || '', readOnly: readonly, required: this.required, spellcheck: this.spellcheck, step: this.step, type: this.type, value: value, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeydown, onCompositionstart: this.onCompositionStart, onCompositionend: this.onCompositionEnd, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined, "aria-labelledby": this.getLabelledById(), ...this.inheritedAttributes }))), h("div", { key: '82cd1b0458dcd491fd1a46b29764f2b79a969bc6', class: "input-end" }, this.clearInput && !readonly && !disabled && (h("button", { key: 'dfcf0ed81cf36fc61b8e12ed4605ac6b6eb50dad', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
|
|
547
544
|
/**
|
|
548
545
|
* This prevents mobile browsers from
|
|
549
546
|
* blurring the input when the clear
|
|
550
547
|
* button is activated.
|
|
551
548
|
*/
|
|
552
549
|
ev.preventDefault();
|
|
553
|
-
}, onClick: this.clearTextInput }, h("ion-icon", { key: '
|
|
550
|
+
}, onClick: this.clearTextInput }, h("ion-icon", { key: '11e2c866135f332e81b4637de178e1c5c7da0d38', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: 'afb0e75e7103b2bf925a9c7649c93eb1d5702670', name: "end" })), shouldRenderHighlight && h("div", { key: '1f305bba279e93685d7139acf0aff6bf4a77500c', class: "input-highlight" })), this.renderBottomContent()));
|
|
554
551
|
}
|
|
555
552
|
get el() { return getElement(this); }
|
|
556
553
|
static get watchers() { return {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { r as registerInstance, c as createEvent, e as config, f as printIonWarning, h, d as Host, g as getElement, i as forceUpdate } from './index-C23AVPx9.js';
|
|
5
5
|
import { E as ENABLE_HTML_CONTENT_DEFAULT } from './config-DWCzVL3Y.js';
|
|
6
|
-
import { c as createSlotMutationController, a as createNotchController, b as createStartContainerController } from './slot-mutation-controller-
|
|
6
|
+
import { g as getSlottedClickContent, c as createSlotMutationController, a as createNotchController, b as createStartContainerController, d as createClickController } from './slot-mutation-controller-BVnANfIt.js';
|
|
7
7
|
import { c as compareOptions, i as isOptionSelected } from './compare-with-utils-sObYyvOy.js';
|
|
8
8
|
import { c as checkInvalidState } from './validity-DJztqcrH.js';
|
|
9
9
|
import { b as inheritAttributes, a as renderHiddenInput, n as focusVisibleElement } from './helpers-BSmc0nst.js';
|
|
@@ -104,41 +104,21 @@ const Select = class {
|
|
|
104
104
|
*/
|
|
105
105
|
this.required = false;
|
|
106
106
|
this.onClick = (ev) => {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
*
|
|
120
|
-
* Clicking a slotted element also causes a click
|
|
121
|
-
* on the <label> element (since it wraps the slots).
|
|
122
|
-
* Clicking <label> dispatches another click event on
|
|
123
|
-
* the native form control that then bubbles up to this
|
|
124
|
-
* listener. This additional event targets the host
|
|
125
|
-
* element, so the select overlay is opened.
|
|
126
|
-
*
|
|
127
|
-
* When the slotted elements are clicked (and therefore
|
|
128
|
-
* the ancestor <label> element) we want to prevent the label
|
|
129
|
-
* from dispatching another click event.
|
|
130
|
-
*
|
|
131
|
-
* Do not call stopPropagation() because this will cause
|
|
132
|
-
* click handlers on the slotted elements to never fire in React.
|
|
133
|
-
* When developers do onClick in React a native "click" listener
|
|
134
|
-
* is added on the root element, not the slotted element. When that
|
|
135
|
-
* native click listener fires, React then dispatches the synthetic
|
|
136
|
-
* click event on the slotted element. However, if stopPropagation
|
|
137
|
-
* is called then the native click event will never bubble up
|
|
138
|
-
* to the root element.
|
|
139
|
-
*/
|
|
140
|
-
ev.preventDefault();
|
|
107
|
+
const slotted = getSlottedClickContent(ev, this.el);
|
|
108
|
+
/**
|
|
109
|
+
* Interactive slotted content, such as a button or a checkbox, handles its
|
|
110
|
+
* own click, so it should not open the select as well. Any other slotted
|
|
111
|
+
* content is decorative and behaves the same as clicking the select
|
|
112
|
+
* itself.
|
|
113
|
+
*/
|
|
114
|
+
if (slotted !== null) {
|
|
115
|
+
const interactive = ev.target.closest(INTERACTIVE_SLOTTED_CONTENT);
|
|
116
|
+
if (interactive !== null && slotted.contains(interactive)) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
141
119
|
}
|
|
120
|
+
this.setFocus();
|
|
121
|
+
this.open(ev);
|
|
142
122
|
};
|
|
143
123
|
this.onFocus = () => {
|
|
144
124
|
this.hasFocus = true;
|
|
@@ -209,6 +189,7 @@ const Select = class {
|
|
|
209
189
|
return getIonMode(this) === 'md' && this.fill === 'outline';
|
|
210
190
|
});
|
|
211
191
|
this.startContainerController.calculateStartContainerWidth();
|
|
192
|
+
this.clickController = createClickController(el);
|
|
212
193
|
this.updateOverlayOptions();
|
|
213
194
|
this.emitStyle();
|
|
214
195
|
this.mutationO = watchForOptions(this.el, 'ion-select-option', async () => {
|
|
@@ -707,6 +688,18 @@ const Select = class {
|
|
|
707
688
|
};
|
|
708
689
|
this.ionStyle.emit(style);
|
|
709
690
|
}
|
|
691
|
+
/**
|
|
692
|
+
* The label wrapping the slots has no `for` attribute, so the browser
|
|
693
|
+
* forwards a click on slotted content to the label's first labelable
|
|
694
|
+
* descendant, the internal button. That forwarded click bubbles back out of
|
|
695
|
+
* the shadow root targeting the host, where it would be emitted a second
|
|
696
|
+
* time and open the select. The controller swallows it during the capture
|
|
697
|
+
* phase, leaving the click on the slotted content itself alone so slotted
|
|
698
|
+
* links, checkboxes and buttons keep their default behavior.
|
|
699
|
+
*/
|
|
700
|
+
onClickCapture(ev) {
|
|
701
|
+
this.clickController?.handleClickCapture(ev);
|
|
702
|
+
}
|
|
710
703
|
renderLabel() {
|
|
711
704
|
const { label } = this;
|
|
712
705
|
return (h("div", { class: {
|
|
@@ -904,7 +897,7 @@ const Select = class {
|
|
|
904
897
|
const hasOutlineFill = mode === 'md' && fill === 'outline';
|
|
905
898
|
renderHiddenInput(true, el, name, parseValue(value), disabled);
|
|
906
899
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus || isExpanded));
|
|
907
|
-
return (h(Host, { key: '
|
|
900
|
+
return (h(Host, { key: '31585557ac49c439939b5a084867f5039dc2f1ce', onClick: this.onClick, class: createColorClasses(this.color, {
|
|
908
901
|
[mode]: true,
|
|
909
902
|
'in-item': inItem,
|
|
910
903
|
'in-item-color': hostContext('ion-item.ion-color', el),
|
|
@@ -922,7 +915,7 @@ const Select = class {
|
|
|
922
915
|
[`select-justify-${justify}`]: justifyEnabled,
|
|
923
916
|
[`select-shape-${shape}`]: shape !== undefined,
|
|
924
917
|
[`select-label-placement-${labelPlacement}`]: true,
|
|
925
|
-
}) }, h("label", { key: '
|
|
918
|
+
}) }, h("label", { key: '368f5f0c77550be6a743752dd1acbfe9915f0a3d', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick, part: "wrapper" }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '2e9039eaa15f398ebe54808ff659db89d3621681', class: "select-start", part: "start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: 'b83e362f89bd8cbc564ad7122778e3a0f08be66d', name: "start" })), h("div", { key: 'f55ac9fc3bbd9a4a3819a101a3780fc02ae8a8e0', class: "select-control", part: "control" }, this.renderLabel(), h("div", { key: 'e32984e57cda5cf341af8e354a2aef5a114827c0', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox(), !hasFloatingOrStackedLabel && this.renderSelectIcon())), h("div", { key: 'fc4cb7c392c46129afe709d9557ad9a10d72b564', class: "select-end", part: "end" }, hasFloatingOrStackedLabel && this.renderSelectIcon(), h("slot", { key: '6d65c92cdafe985a769fb3b41a58625077803eef', name: "end" })), shouldRenderHighlight && h("div", { key: '9a912c64d7cd77f074a069d126cd0bada1b71eed', class: "select-highlight" })), this.renderBottomContent()));
|
|
926
919
|
}
|
|
927
920
|
get el() { return getElement(this); }
|
|
928
921
|
static get watchers() { return {
|
|
@@ -1179,6 +1172,33 @@ const extractOptionContent = (option, customHTMLEnabled) => {
|
|
|
1179
1172
|
};
|
|
1180
1173
|
let selectIds = 0;
|
|
1181
1174
|
const OPTION_CLASS = 'select-interface-option';
|
|
1175
|
+
/**
|
|
1176
|
+
* Slotted content that handles its own click, so clicking it should not also
|
|
1177
|
+
* open the select.
|
|
1178
|
+
*
|
|
1179
|
+
* This deliberately does not reuse `focusableQueryString`. That selector
|
|
1180
|
+
* answers whether an element can take focus right now, which is a different
|
|
1181
|
+
* question: an ion-radio outside a radio group carries tabindex="-1" from the
|
|
1182
|
+
* group's roving tabindex, and disabled controls are excluded, yet both still
|
|
1183
|
+
* handle their own clicks.
|
|
1184
|
+
*/
|
|
1185
|
+
const INTERACTIVE_SLOTTED_CONTENT = [
|
|
1186
|
+
'a[href]',
|
|
1187
|
+
'button',
|
|
1188
|
+
'input[type="checkbox"]',
|
|
1189
|
+
'input[type="radio"]',
|
|
1190
|
+
'ion-button',
|
|
1191
|
+
'ion-checkbox',
|
|
1192
|
+
'ion-radio',
|
|
1193
|
+
'ion-toggle',
|
|
1194
|
+
'[tabindex]:not([tabindex^="-"])',
|
|
1195
|
+
/**
|
|
1196
|
+
* Covers the remaining Ionic controls. The tags above are still listed
|
|
1197
|
+
* because ion-checkbox and ion-radio only carry this class when they are
|
|
1198
|
+
* outside an item, so a select inside an item would lose the match.
|
|
1199
|
+
*/
|
|
1200
|
+
'.ion-focusable',
|
|
1201
|
+
].join(', ');
|
|
1182
1202
|
Select.style = {
|
|
1183
1203
|
ios: selectIosCss(),
|
|
1184
1204
|
md: selectMdCss()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
3
|
*/
|
|
4
4
|
import { r as registerInstance, c as createEvent, i as forceUpdate, w as writeTask, h, d as Host, g as getElement } from './index-C23AVPx9.js';
|
|
5
|
-
import { c as createSlotMutationController, a as createNotchController, b as createStartContainerController } from './slot-mutation-controller-
|
|
5
|
+
import { c as createSlotMutationController, a as createNotchController, b as createStartContainerController, d as createClickController } from './slot-mutation-controller-BVnANfIt.js';
|
|
6
6
|
import { c as checkInvalidState } from './validity-DJztqcrH.js';
|
|
7
7
|
import { d as debounceEvent, b as inheritAttributes, i as inheritAriaAttributes, c as componentOnReady } from './helpers-BSmc0nst.js';
|
|
8
8
|
import { h as hostContext, c as createColorClasses } from './theme-byZM6qHV.js';
|
|
@@ -194,11 +194,7 @@ const Textarea = class {
|
|
|
194
194
|
* Instead, the click event from the ion-textarea is emitted.
|
|
195
195
|
*/
|
|
196
196
|
onClickCapture(ev) {
|
|
197
|
-
|
|
198
|
-
if (nativeInput && ev.target === nativeInput) {
|
|
199
|
-
ev.stopPropagation();
|
|
200
|
-
this.el.click();
|
|
201
|
-
}
|
|
197
|
+
this.clickController?.handleClickCapture(ev);
|
|
202
198
|
}
|
|
203
199
|
connectedCallback() {
|
|
204
200
|
const { el } = this;
|
|
@@ -211,6 +207,7 @@ const Textarea = class {
|
|
|
211
207
|
return getIonMode(this) === 'md' && this.fill === 'outline';
|
|
212
208
|
});
|
|
213
209
|
this.startContainerController.calculateStartContainerWidth();
|
|
210
|
+
this.clickController = createClickController(el, () => this.nativeInput);
|
|
214
211
|
// Watch for class changes to update validation state
|
|
215
212
|
if (typeof MutationObserver !== 'undefined') {
|
|
216
213
|
this.validationObserver = new MutationObserver(() => {
|
|
@@ -466,7 +463,7 @@ const Textarea = class {
|
|
|
466
463
|
* the textarea has a value or is focused.
|
|
467
464
|
*/
|
|
468
465
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || hasFocus));
|
|
469
|
-
return (h(Host, { key: '
|
|
466
|
+
return (h(Host, { key: '0f01ed6565313c45beaea5e0aa429a86b9cfa1a0', class: createColorClasses(this.color, {
|
|
470
467
|
[mode]: true,
|
|
471
468
|
'has-value': hasValue,
|
|
472
469
|
'has-focus': hasFocus,
|
|
@@ -475,7 +472,7 @@ const Textarea = class {
|
|
|
475
472
|
[`textarea-shape-${shape}`]: shape !== undefined,
|
|
476
473
|
[`textarea-label-placement-${labelPlacement}`]: true,
|
|
477
474
|
'textarea-disabled': disabled,
|
|
478
|
-
}) }, h("label", { key: '
|
|
475
|
+
}) }, h("label", { key: 'a59548c3d3bf66543fd6e06b5c3b6e649f963164', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '29055193d664d9fe84ba0340fdcbbc54fec0e403', class: "textarea-start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: '2cc122d9541ce7ae3c2309c27123b6c6c91cbfc7', name: "start" })), h("div", { key: 'c847a6fa1eaf6ef2b296415cd72bdd3aee9828a2', class: "textarea-control" }, this.renderLabel(), h("div", { key: 'f9205f82d6346daa86dc7e0f6e1eb221c76ed7b0', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", { key: '0bdb517fa5b3a44eeb3bb2ed1f9a8eab21a9a6a1', class: "native-textarea", ref: (el) => (this.nativeInput = el), id: inputId, disabled: disabled, autoCapitalize: this.autocapitalize, autoFocus: this.autofocus, enterKeyHint: this.enterkeyhint, inputMode: this.inputmode, minLength: this.minlength, maxLength: this.maxlength, name: this.name, placeholder: this.placeholder || '', readOnly: this.readonly, required: this.required, spellcheck: this.spellcheck, cols: this.cols, rows: this.rows, wrap: this.wrap, onInput: this.onInput, onChange: this.onChange, onBlur: this.onBlur, onFocus: this.onFocus, onKeyDown: this.onKeyDown, "aria-describedby": this.getHintTextID(), "aria-invalid": this.isInvalid ? 'true' : undefined, ...this.inheritedAttributes }, value))), h("div", { key: '1ce661b800ef12146bdb97fb193e22be227ea052', class: "textarea-end" }, h("slot", { key: 'aca5704db48fe8e0e784cf57f2978ae2d63b3edc', name: "end" })), shouldRenderHighlight && h("div", { key: 'd916816d44cbdb4bc9a05032c5490eaa9bf3ba47', class: "textarea-highlight" })), this.renderBottomContent()));
|
|
479
476
|
}
|
|
480
477
|
get el() { return getElement(this); }
|
|
481
478
|
static get watchers() { return {
|