@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.
Files changed (40) hide show
  1. package/components/ion-input.js +1 -1
  2. package/components/ion-select.js +1 -1
  3. package/components/ion-textarea.js +1 -1
  4. package/components/p-CobW5muY.js +4 -0
  5. package/dist/cjs/ion-input.cjs.entry.js +6 -9
  6. package/dist/cjs/ion-select_3.cjs.entry.js +57 -37
  7. package/dist/cjs/ion-textarea.cjs.entry.js +5 -8
  8. package/dist/cjs/ionic.cjs.js +1 -1
  9. package/dist/cjs/loader.cjs.js +1 -1
  10. package/dist/cjs/{slot-mutation-controller-D6IjSEIh.js → slot-mutation-controller-yNStVQqX.js} +69 -0
  11. package/dist/collection/components/input/input.js +6 -9
  12. package/dist/collection/components/select/select.js +67 -38
  13. package/dist/collection/components/textarea/textarea.js +5 -8
  14. package/dist/collection/utils/forms/click-controller.js +70 -0
  15. package/dist/collection/utils/forms/index.js +1 -0
  16. package/dist/docs.json +8 -2
  17. package/dist/esm/ion-input.entry.js +6 -9
  18. package/dist/esm/ion-select_3.entry.js +57 -37
  19. package/dist/esm/ion-textarea.entry.js +5 -8
  20. package/dist/esm/ionic.js +1 -1
  21. package/dist/esm/loader.js +1 -1
  22. package/dist/esm/{slot-mutation-controller-B5NpUZ-N.js → slot-mutation-controller-BVnANfIt.js} +68 -1
  23. package/dist/ionic/ionic.esm.js +1 -1
  24. package/dist/ionic/p-39b6b6ba.entry.js +4 -0
  25. package/dist/ionic/p-4d7779c8.entry.js +4 -0
  26. package/dist/ionic/p-91407d16.entry.js +4 -0
  27. package/dist/ionic/p-DcCEP7Ss.js +4 -0
  28. package/dist/types/components/input/input.d.ts +1 -0
  29. package/dist/types/components/select/select.d.ts +11 -0
  30. package/dist/types/components/textarea/textarea.d.ts +1 -0
  31. package/dist/types/utils/forms/click-controller.d.ts +36 -0
  32. package/dist/types/utils/forms/index.d.ts +1 -0
  33. package/hydrate/index.js +133 -52
  34. package/hydrate/index.mjs +133 -52
  35. package/package.json +1 -1
  36. package/components/p-Cw6WocLJ.js +0 -4
  37. package/dist/ionic/p-7e394cc3.entry.js +0 -4
  38. package/dist/ionic/p-CkxVYPtX.js +0 -4
  39. package/dist/ionic/p-a20a2ebe.entry.js +0 -4
  40. 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 target = ev.target;
113
- const closestSlot = target.closest('[slot="start"], [slot="end"]');
114
- if (target === this.el || closestSlot === null) {
115
- this.setFocus();
116
- this.open(ev);
117
- }
118
- else {
119
- /**
120
- * Prevent clicks to the start/end slots from opening the select.
121
- * We ensure the target isn't this element in case the select is slotted
122
- * in, for example, an item. This would prevent the select from ever
123
- * being opened since the element itself has slot="start"/"end".
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: 'b2f161e3706e139e61440cd13e162714b1b75d72', onClick: this.onClick, class: createColorClasses(this.color, {
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: '944a2eb468154a21260d181fdaf67828a71eb902', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick, part: "wrapper" }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '927200c53548b1bb4f52d6a167605b724a7b7d0d', class: "select-start", part: "start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: '6e14b95e9b95c116b16744d630600e90977782bc', name: "start" })), h("div", { key: '8451ed4300ac005963075be36a04ea8763bc0fdd', class: "select-control", part: "control" }, this.renderLabel(), h("div", { key: '207e7b63f3224a4870579365ac992da6784fb31f', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox(), !hasFloatingOrStackedLabel && this.renderSelectIcon())), h("div", { key: '5c17367d488db4d570f31b25e33560efa9011500', class: "select-end", part: "end" }, hasFloatingOrStackedLabel && this.renderSelectIcon(), h("slot", { key: '21fd8c0d9bee8cffef9ef6984cacd31e8f38211c', name: "end" })), shouldRenderHighlight && h("div", { key: '1fff767db9a1ec07b274ef65ff7ae8fb976e6ad2', class: "select-highlight" })), this.renderBottomContent()));
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
- const nativeInput = this.nativeInput;
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: '78da9c96e6a29f19b237f268dd8bfd044afe27b6', class: createColorClasses(this.color, {
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: 'c5ea3f26e01e66b857d3a1c88dbafbf2dbb81fdb', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '5a9d0efc8c13d9bd4ec4ceadf1bdb84205c47b3d', class: "textarea-start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: 'da08262e33f3a5b454847297338a74f07c5b60c9', name: "start" })), h("div", { key: '6461dc7381929eb83679ec07563b7bdf8bfc3f9f', class: "textarea-control" }, this.renderLabel(), h("div", { key: '8f7902a9e6040384d25d0e2061c04f6b49acf187', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", { key: '2149d8b2c23a164c536327c98d7a632e1e85428a', 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: '84f5f42add89875c3f0cce734b512a9e7be81e03', class: "textarea-end" }, h("slot", { key: 'ea654d78013a00b8af33deb89d42c2b7f6c9cb99', name: "end" })), shouldRenderHighlight && h("div", { key: 'b72195d2606816b3b71e65214d78cef1740f4804', class: "textarea-highlight" })), this.renderBottomContent()));
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
+ };
@@ -5,4 +5,5 @@ export * from './notch-controller';
5
5
  export * from './start-container-controller';
6
6
  export * from './compare-with-utils';
7
7
  export * from './item-multiple-inputs';
8
+ export * from './click-controller';
8
9
  export * from './validity';
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-08-31T18:44:40",
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-B5NpUZ-N.js';
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
- const nativeInput = this.nativeInput;
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: '9e9495b8f962372d526343ff76b80fb9700fce19', class: createColorClasses(this.color, {
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: '7121f34ed782caf7a7b82703b94fc9d6c78ce5ba', class: "input-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '0c1f8526d86c8a2433c23873fa64b1edc0eafe29', class: "input-start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: 'a50323fde0fb12997c2324eded132908680ea450', name: "start" })), h("div", { key: '626360c4e115a5286baab49680b2a02cd39614c8', class: "input-control" }, this.renderLabel(), h("div", { key: '1cefd9d8cd6ed96fab7b58237f8f8b0c5e1be92e', class: "native-wrapper", onClick: this.onLabelClick }, h("input", { key: '3dee5302f3a685a848fedd7e1face5095e3ac64b', 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: '39fff82e00e63963fe5f17794900ace2a3faa208', class: "input-end" }, this.clearInput && !readonly && !disabled && (h("button", { key: '1fa86fdb5baf4a7cd531ee0fe767a42b44fb33fc', "aria-label": "reset", type: "button", class: "input-clear-icon", onPointerDown: (ev) => {
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: 'ced39304f97e56870f7480afa025ffe232010cfb', "aria-hidden": "true", icon: clearIconData }))), h("slot", { key: 'f4098769eef1cc5aa2074c369f757ccb94a98e1c', name: "end" })), shouldRenderHighlight && h("div", { key: '258fe6ff8f9f57836f2a3a0b6163bd2b7dbf0699', class: "input-highlight" })), this.renderBottomContent()));
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-B5NpUZ-N.js';
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 target = ev.target;
108
- const closestSlot = target.closest('[slot="start"], [slot="end"]');
109
- if (target === this.el || closestSlot === null) {
110
- this.setFocus();
111
- this.open(ev);
112
- }
113
- else {
114
- /**
115
- * Prevent clicks to the start/end slots from opening the select.
116
- * We ensure the target isn't this element in case the select is slotted
117
- * in, for example, an item. This would prevent the select from ever
118
- * being opened since the element itself has slot="start"/"end".
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: 'b2f161e3706e139e61440cd13e162714b1b75d72', onClick: this.onClick, class: createColorClasses(this.color, {
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: '944a2eb468154a21260d181fdaf67828a71eb902', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick, part: "wrapper" }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '927200c53548b1bb4f52d6a167605b724a7b7d0d', class: "select-start", part: "start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: '6e14b95e9b95c116b16744d630600e90977782bc', name: "start" })), h("div", { key: '8451ed4300ac005963075be36a04ea8763bc0fdd', class: "select-control", part: "control" }, this.renderLabel(), h("div", { key: '207e7b63f3224a4870579365ac992da6784fb31f', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox(), !hasFloatingOrStackedLabel && this.renderSelectIcon())), h("div", { key: '5c17367d488db4d570f31b25e33560efa9011500', class: "select-end", part: "end" }, hasFloatingOrStackedLabel && this.renderSelectIcon(), h("slot", { key: '21fd8c0d9bee8cffef9ef6984cacd31e8f38211c', name: "end" })), shouldRenderHighlight && h("div", { key: '1fff767db9a1ec07b274ef65ff7ae8fb976e6ad2', class: "select-highlight" })), this.renderBottomContent()));
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-B5NpUZ-N.js';
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
- const nativeInput = this.nativeInput;
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: '78da9c96e6a29f19b237f268dd8bfd044afe27b6', class: createColorClasses(this.color, {
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: 'c5ea3f26e01e66b857d3a1c88dbafbf2dbb81fdb', class: "textarea-wrapper", htmlFor: inputId, onClick: this.onLabelClick }, hasOutlineFill && this.renderOutlineContainer(), h("div", { key: '5a9d0efc8c13d9bd4ec4ceadf1bdb84205c47b3d', class: "textarea-start", ref: (el) => (this.startContainerEl = el) }, h("slot", { key: 'da08262e33f3a5b454847297338a74f07c5b60c9', name: "start" })), h("div", { key: '6461dc7381929eb83679ec07563b7bdf8bfc3f9f', class: "textarea-control" }, this.renderLabel(), h("div", { key: '8f7902a9e6040384d25d0e2061c04f6b49acf187', class: "native-wrapper", ref: (el) => (this.textareaWrapper = el) }, h("textarea", { key: '2149d8b2c23a164c536327c98d7a632e1e85428a', 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: '84f5f42add89875c3f0cce734b512a9e7be81e03', class: "textarea-end" }, h("slot", { key: 'ea654d78013a00b8af33deb89d42c2b7f6c9cb99', name: "end" })), shouldRenderHighlight && h("div", { key: 'b72195d2606816b3b71e65214d78cef1740f4804', class: "textarea-highlight" })), this.renderBottomContent()));
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 {