@ionic/core 8.8.4-dev.11776178970.14763043 → 8.8.4-dev.11776186452.1cc0af05
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-action-sheet.js +1 -1
- package/components/ion-alert.js +1 -1
- package/components/ion-infinite-scroll-content.js +1 -1
- package/components/ion-loading.js +1 -1
- package/components/ion-range.js +1 -1
- package/components/ion-refresher-content.js +1 -1
- package/components/ion-select-modal.js +1 -1
- package/components/ion-select-option.js +1 -1
- package/components/ion-select-popover.js +1 -1
- package/components/ion-select.js +1 -1
- package/components/ion-skeleton-text.js +1 -1
- package/components/ion-spinner.js +1 -1
- package/components/ion-split-pane.js +1 -1
- package/components/ion-tab-bar.js +1 -1
- package/components/ion-tab-button.js +1 -1
- package/components/ion-tab.js +1 -1
- package/components/ion-tabs.js +1 -1
- package/components/ion-text.js +1 -1
- package/components/ion-textarea.js +1 -1
- package/components/ion-thumbnail.js +1 -1
- package/components/ion-title.js +1 -1
- package/components/ion-toast.js +1 -1
- package/components/ion-toggle.js +1 -1
- package/components/ion-toolbar.js +1 -1
- package/components/{p-C-_EGKki.js → p-B0orUoaV.js} +1 -1
- package/components/p-BObmvbuR.js +4 -0
- package/components/p-C8Dne7pI.js +4 -0
- package/components/p-Cm7hjN9B.js +4 -0
- package/components/p-DQY5lHUa.js +4 -0
- package/components/p-D_uMZULz.js +4 -0
- package/components/{p-CoarhFWH.js → p-ZRp3l6Dk.js} +1 -1
- package/components/{p-C2cZvGcF.js → p-qhBzWoOF.js} +1 -1
- package/dist/cjs/ion-action-sheet.cjs.entry.js +23 -8
- package/dist/cjs/ion-alert.cjs.entry.js +42 -24
- package/dist/cjs/ion-app_8.cjs.entry.js +4 -4
- package/dist/cjs/ion-avatar_3.cjs.entry.js +2 -2
- package/dist/cjs/ion-item_8.cjs.entry.js +2 -2
- package/dist/cjs/ion-range.cjs.entry.js +1 -1
- package/dist/cjs/ion-select-modal.cjs.entry.js +59 -26
- package/dist/cjs/ion-select_3.cjs.entry.js +190 -47
- package/dist/cjs/ion-spinner.cjs.entry.js +1 -1
- package/dist/cjs/ion-split-pane.cjs.entry.js +2 -2
- package/dist/cjs/ion-tab-bar_2.cjs.entry.js +4 -4
- package/dist/cjs/ion-tab_2.cjs.entry.js +3 -3
- package/dist/cjs/ion-text.cjs.entry.js +2 -2
- package/dist/cjs/ion-textarea.cjs.entry.js +3 -3
- package/dist/cjs/ion-toast.cjs.entry.js +2 -2
- package/dist/cjs/ion-toggle.cjs.entry.js +3 -3
- package/dist/cjs/ionic.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/select-option-render-qQf4xz8P.js +67 -0
- package/dist/collection/components/action-sheet/action-sheet.ionic.css +834 -0
- package/dist/collection/components/action-sheet/action-sheet.ios.css +84 -0
- package/dist/collection/components/action-sheet/action-sheet.js +21 -6
- package/dist/collection/components/action-sheet/action-sheet.md.css +84 -0
- package/dist/collection/components/alert/alert.ionic.css +1165 -0
- package/dist/collection/components/alert/alert.ios.css +83 -0
- package/dist/collection/components/alert/alert.js +40 -22
- package/dist/collection/components/alert/alert.md.css +83 -0
- package/dist/collection/components/range/range.ionic.css +6 -9
- package/dist/collection/components/select/select.ionic.css +31 -0
- package/dist/collection/components/select/select.ios.css +31 -0
- package/dist/collection/components/select/select.js +125 -14
- package/dist/collection/components/select/select.md.css +31 -0
- package/dist/collection/components/select-modal/select-modal.ionic.css +24 -0
- package/dist/collection/components/select-modal/select-modal.ios.css +77 -0
- package/dist/collection/components/select-modal/select-modal.js +56 -23
- package/dist/collection/components/select-modal/select-modal.md.css +80 -1
- package/dist/collection/components/select-option/select-option.js +21 -2
- package/dist/collection/components/select-popover/select-popover.ionic.css +489 -0
- package/dist/collection/components/select-popover/select-popover.ios.css +77 -0
- package/dist/collection/components/select-popover/select-popover.js +59 -26
- package/dist/collection/components/select-popover/select-popover.md.css +77 -0
- package/dist/collection/components/skeleton-text/skeleton-text.js +2 -2
- package/dist/collection/components/spinner/spinner.js +1 -1
- package/dist/collection/components/split-pane/split-pane.js +2 -2
- package/dist/collection/components/tab/tab.js +2 -2
- package/dist/collection/components/tab-bar/tab-bar.js +2 -2
- package/dist/collection/components/tab-button/tab-button.js +2 -2
- package/dist/collection/components/tabs/tabs.js +1 -1
- package/dist/collection/components/text/text.js +2 -2
- package/dist/collection/components/textarea/textarea.js +3 -3
- package/dist/collection/components/thumbnail/thumbnail.js +2 -2
- package/dist/collection/components/title/title.js +2 -2
- package/dist/collection/components/toast/toast.js +2 -2
- package/dist/collection/components/toggle/toggle.js +3 -3
- package/dist/collection/components/toolbar/toolbar.js +2 -2
- package/dist/collection/utils/select-option-render.js +62 -0
- package/dist/docs.json +135 -1
- package/dist/esm/ion-action-sheet.entry.js +23 -8
- package/dist/esm/ion-alert.entry.js +42 -24
- package/dist/esm/ion-app_8.entry.js +4 -4
- package/dist/esm/ion-avatar_3.entry.js +2 -2
- package/dist/esm/ion-item_8.entry.js +2 -2
- package/dist/esm/ion-range.entry.js +1 -1
- package/dist/esm/ion-select-modal.entry.js +59 -26
- package/dist/esm/ion-select_3.entry.js +191 -48
- package/dist/esm/ion-spinner.entry.js +1 -1
- package/dist/esm/ion-split-pane.entry.js +2 -2
- package/dist/esm/ion-tab-bar_2.entry.js +4 -4
- package/dist/esm/ion-tab_2.entry.js +3 -3
- package/dist/esm/ion-text.entry.js +2 -2
- package/dist/esm/ion-textarea.entry.js +3 -3
- package/dist/esm/ion-toast.entry.js +2 -2
- package/dist/esm/ion-toggle.entry.js +3 -3
- package/dist/esm/ionic.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/select-option-render-BQUT9Aqa.js +65 -0
- package/dist/html.html-data.json +4 -0
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-003b40fc.entry.js +4 -0
- package/dist/ionic/{p-3bf01c2c.entry.js → p-26595044.entry.js} +1 -1
- package/dist/ionic/p-27edb91a.entry.js +4 -0
- package/dist/ionic/p-35aa95dc.entry.js +4 -0
- package/dist/ionic/{p-e9d6ce67.entry.js → p-37f895a8.entry.js} +1 -1
- package/dist/ionic/p-38897781.entry.js +4 -0
- package/dist/ionic/p-575061c0.entry.js +4 -0
- package/dist/ionic/{p-2bf931ae.entry.js → p-69033ad6.entry.js} +1 -1
- package/dist/ionic/{p-cdfbe4cc.entry.js → p-71f28573.entry.js} +1 -1
- package/dist/ionic/{p-9b9b1450.entry.js → p-96ec9a10.entry.js} +1 -1
- package/dist/ionic/{p-49799a34.entry.js → p-9e71982e.entry.js} +1 -1
- package/dist/ionic/p-Dr3N4o63.js +4 -0
- package/dist/ionic/{p-bc5713f7.entry.js → p-a1c8ba8c.entry.js} +1 -1
- package/dist/ionic/{p-91e242e4.entry.js → p-a94016be.entry.js} +1 -1
- package/dist/ionic/{p-6c8c37c2.entry.js → p-aa8c1e64.entry.js} +1 -1
- package/dist/ionic/p-c3335fe0.entry.js +4 -0
- package/dist/ionic/{p-2193e875.entry.js → p-d2fe6791.entry.js} +1 -1
- package/dist/types/components/select/select-interface.d.ts +21 -0
- package/dist/types/components/select/select.d.ts +7 -0
- package/dist/types/components/select-option/select-option.d.ts +4 -0
- package/dist/types/components.d.ts +9 -0
- package/dist/types/utils/select-option-render.d.ts +26 -0
- package/hydrate/index.js +396 -132
- package/hydrate/index.mjs +396 -132
- package/package.json +1 -1
- package/components/p-BGHaEUgp.js +0 -4
- package/components/p-BR9Yxas9.js +0 -4
- package/components/p-Ch9P0ikq.js +0 -4
- package/components/p-iwGbwewM.js +0 -4
- package/dist/ionic/p-227744b8.entry.js +0 -4
- package/dist/ionic/p-4b0f5ffd.entry.js +0 -4
- package/dist/ionic/p-64341e32.entry.js +0 -4
- package/dist/ionic/p-6be2b2d3.entry.js +0 -4
- package/dist/ionic/p-9acd3fd3.entry.js +0 -4
- package/dist/ionic/p-a283aa4d.entry.js +0 -4
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import caretDownRegular from "@phosphor-icons/core/assets/regular/caret-down.svg";
|
|
5
5
|
import { Build, Host, h, forceUpdate } from "@stencil/core";
|
|
6
|
+
import { ENABLE_HTML_CONTENT_DEFAULT } from "../../utils/config";
|
|
6
7
|
import { compareOptions, createNotchController, isOptionSelected, checkInvalidState } from "../../utils/forms/index";
|
|
7
8
|
import { focusVisibleElement, renderHiddenInput, inheritAttributes } from "../../utils/helpers";
|
|
8
9
|
import { printIonWarning } from "../../utils/logging/index";
|
|
9
10
|
import { actionSheetController, alertController, popoverController, modalController } from "../../utils/overlays";
|
|
10
11
|
import { isRTL } from "../../utils/rtl/index";
|
|
12
|
+
import { sanitizeDOMString } from "../../utils/sanitization/index";
|
|
11
13
|
import { createColorClasses, hostContext } from "../../utils/theme";
|
|
12
14
|
import { watchForOptions } from "../../utils/watch-options";
|
|
13
15
|
import { caretDownSharp, chevronExpand } from "ionicons/icons";
|
|
@@ -40,6 +42,7 @@ export class Select {
|
|
|
40
42
|
this.helperTextId = `${this.inputId}-helper-text`;
|
|
41
43
|
this.errorTextId = `${this.inputId}-error-text`;
|
|
42
44
|
this.inheritedAttributes = {};
|
|
45
|
+
this.customHTMLEnabled = config.get('innerHTMLTemplatesEnabled', ENABLE_HTML_CONTENT_DEFAULT);
|
|
43
46
|
this.isExpanded = false;
|
|
44
47
|
/**
|
|
45
48
|
* The `hasFocus` state ensures the focus class is
|
|
@@ -402,9 +405,14 @@ export class Select {
|
|
|
402
405
|
.join(' ');
|
|
403
406
|
const optClass = `${OPTION_CLASS} ${copyClasses}`;
|
|
404
407
|
const isSelected = isOptionSelected(selectValue, value, this.compareWith);
|
|
408
|
+
const text = this.customHTMLEnabled ? getOptionContent(option) : option.textContent;
|
|
409
|
+
const startContent = this.customHTMLEnabled
|
|
410
|
+
? getOptionContent(option, 'start')
|
|
411
|
+
: undefined;
|
|
412
|
+
const endContent = this.customHTMLEnabled ? getOptionContent(option, 'end') : undefined;
|
|
405
413
|
return {
|
|
406
414
|
role: isSelected ? 'selected' : '',
|
|
407
|
-
text:
|
|
415
|
+
text: text !== null && text !== void 0 ? text : '',
|
|
408
416
|
cssClass: optClass,
|
|
409
417
|
handler: () => {
|
|
410
418
|
this.setValue(value);
|
|
@@ -413,6 +421,9 @@ export class Select {
|
|
|
413
421
|
'aria-checked': isSelected ? 'true' : 'false',
|
|
414
422
|
role: 'radio',
|
|
415
423
|
},
|
|
424
|
+
startContent: startContent !== null && startContent !== void 0 ? startContent : undefined,
|
|
425
|
+
endContent: endContent !== null && endContent !== void 0 ? endContent : undefined,
|
|
426
|
+
description: option.description,
|
|
416
427
|
};
|
|
417
428
|
});
|
|
418
429
|
// Add "cancel" button
|
|
@@ -433,13 +444,21 @@ export class Select {
|
|
|
433
444
|
.filter((cls) => cls !== 'hydrated')
|
|
434
445
|
.join(' ');
|
|
435
446
|
const optClass = `${OPTION_CLASS} ${copyClasses}`;
|
|
447
|
+
const label = this.customHTMLEnabled ? getOptionContent(option) : option.textContent;
|
|
448
|
+
const startContent = this.customHTMLEnabled
|
|
449
|
+
? getOptionContent(option, 'start')
|
|
450
|
+
: undefined;
|
|
451
|
+
const endContent = this.customHTMLEnabled ? getOptionContent(option, 'end') : undefined;
|
|
436
452
|
return {
|
|
437
453
|
type: inputType,
|
|
438
454
|
cssClass: optClass,
|
|
439
|
-
label:
|
|
455
|
+
label: label !== null && label !== void 0 ? label : '',
|
|
440
456
|
value,
|
|
441
457
|
checked: isOptionSelected(selectValue, value, this.compareWith),
|
|
442
458
|
disabled: option.disabled,
|
|
459
|
+
startContent: startContent !== null && startContent !== void 0 ? startContent : undefined,
|
|
460
|
+
endContent: endContent !== null && endContent !== void 0 ? endContent : undefined,
|
|
461
|
+
description: option.description,
|
|
443
462
|
};
|
|
444
463
|
});
|
|
445
464
|
return alertInputs;
|
|
@@ -452,8 +471,13 @@ export class Select {
|
|
|
452
471
|
.filter((cls) => cls !== 'hydrated')
|
|
453
472
|
.join(' ');
|
|
454
473
|
const optClass = `${OPTION_CLASS} ${copyClasses}`;
|
|
474
|
+
const text = this.customHTMLEnabled ? getOptionContent(option) : option.textContent;
|
|
475
|
+
const startContent = this.customHTMLEnabled
|
|
476
|
+
? getOptionContent(option, 'start')
|
|
477
|
+
: undefined;
|
|
478
|
+
const endContent = this.customHTMLEnabled ? getOptionContent(option, 'end') : undefined;
|
|
455
479
|
return {
|
|
456
|
-
text:
|
|
480
|
+
text: text !== null && text !== void 0 ? text : '',
|
|
457
481
|
cssClass: optClass,
|
|
458
482
|
value,
|
|
459
483
|
checked: isOptionSelected(selectValue, value, this.compareWith),
|
|
@@ -464,6 +488,9 @@ export class Select {
|
|
|
464
488
|
this.close();
|
|
465
489
|
}
|
|
466
490
|
},
|
|
491
|
+
startContent: startContent !== null && startContent !== void 0 ? startContent : undefined,
|
|
492
|
+
endContent: endContent !== null && endContent !== void 0 ? endContent : undefined,
|
|
493
|
+
description: option.description,
|
|
467
494
|
};
|
|
468
495
|
});
|
|
469
496
|
return popoverOptions;
|
|
@@ -631,12 +658,18 @@ export class Select {
|
|
|
631
658
|
}
|
|
632
659
|
return;
|
|
633
660
|
}
|
|
634
|
-
|
|
661
|
+
/**
|
|
662
|
+
* Returns the text to display in the select based on the selected value.
|
|
663
|
+
*
|
|
664
|
+
* @param useHTML If `true`, the returned text will include any custom HTML content from the selected option. If `false`, the returned text will be plain text without any HTML. Defaults to `false`.
|
|
665
|
+
* @returns The text to display in the select, either with or without HTML based on the `useHTML` parameter.
|
|
666
|
+
*/
|
|
667
|
+
getText(useHTML = false) {
|
|
635
668
|
const selectedText = this.selectedText;
|
|
636
669
|
if (selectedText != null && selectedText !== '') {
|
|
637
670
|
return selectedText;
|
|
638
671
|
}
|
|
639
|
-
return generateText(this.childOpts, this.value, this.compareWith);
|
|
672
|
+
return generateText(this.childOpts, this.value, this.compareWith, useHTML);
|
|
640
673
|
}
|
|
641
674
|
setFocus() {
|
|
642
675
|
if (this.focusEl) {
|
|
@@ -725,7 +758,7 @@ export class Select {
|
|
|
725
758
|
*/
|
|
726
759
|
renderSelectText() {
|
|
727
760
|
const { placeholder } = this;
|
|
728
|
-
const displayValue = this.getText();
|
|
761
|
+
const displayValue = this.getText(true);
|
|
729
762
|
let addPlaceholderClass = false;
|
|
730
763
|
let selectText = displayValue;
|
|
731
764
|
if (selectText === '' && placeholder !== undefined) {
|
|
@@ -737,6 +770,9 @@ export class Select {
|
|
|
737
770
|
'select-placeholder': addPlaceholderClass,
|
|
738
771
|
};
|
|
739
772
|
const textPart = addPlaceholderClass ? 'placeholder' : 'text';
|
|
773
|
+
if (this.customHTMLEnabled) {
|
|
774
|
+
return h("div", { "aria-hidden": "true", class: selectTextClasses, part: textPart, innerHTML: selectText });
|
|
775
|
+
}
|
|
740
776
|
return (h("div", { "aria-hidden": "true", class: selectTextClasses, part: textPart }, selectText));
|
|
741
777
|
}
|
|
742
778
|
/**
|
|
@@ -754,6 +790,7 @@ export class Select {
|
|
|
754
790
|
get ariaLabel() {
|
|
755
791
|
var _a;
|
|
756
792
|
const { placeholder, inheritedAttributes } = this;
|
|
793
|
+
// Get the plain text from the selected text
|
|
757
794
|
const displayValue = this.getText();
|
|
758
795
|
// The aria label should be preferred over visible text if both are specified
|
|
759
796
|
const definedLabel = (_a = inheritedAttributes['aria-label']) !== null && _a !== void 0 ? _a : this.labelText;
|
|
@@ -908,7 +945,7 @@ export class Select {
|
|
|
908
945
|
* TODO(FW-5592): Remove hasStartEndSlots condition
|
|
909
946
|
*/
|
|
910
947
|
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
|
|
911
|
-
return (h(Host, { key: '
|
|
948
|
+
return (h(Host, { key: '460ec72063e2f484a1fd0d7605739f4cef2d8c87', onClick: this.onClick, class: createColorClasses(this.color, {
|
|
912
949
|
[theme]: true,
|
|
913
950
|
'in-item': inItem,
|
|
914
951
|
'in-item-color': hostContext('ion-item.ion-color', el),
|
|
@@ -927,7 +964,7 @@ export class Select {
|
|
|
927
964
|
[`select-shape-${shape}`]: shape !== undefined,
|
|
928
965
|
[`select-label-placement-${labelPlacement}`]: true,
|
|
929
966
|
[`select-size-${size}`]: size !== undefined,
|
|
930
|
-
}) }, h("label", { key: '
|
|
967
|
+
}) }, h("label", { key: 'a553837e266ce48e17687ee7ff4027f69a2eff1e', class: "select-wrapper", id: "select-label", onClick: this.onLabelClick, part: "wrapper" }, this.renderLabelContainer(), h("div", { key: 'e5071d8ee5e4078887ac27a192aa95553a9f487d', class: "select-wrapper-inner", part: "inner" },
|
|
931
968
|
/**
|
|
932
969
|
* For the ionic theme, we render the outline container here
|
|
933
970
|
* instead of higher up, so it can be positioned relative to
|
|
@@ -937,7 +974,7 @@ export class Select {
|
|
|
937
974
|
* <label> element, ensuring that clicking the label text
|
|
938
975
|
* focuses the select.
|
|
939
976
|
*/
|
|
940
|
-
theme === 'ionic' && fill === 'outline' && h("div", { key: '
|
|
977
|
+
theme === 'ionic' && fill === 'outline' && h("div", { key: '04a19d0f8deff600f354c1e2682937884e8e3673', class: "select-outline" }), h("slot", { key: '51e50dccee39355a3ac78b615e4a7a71a371a744', name: "start" }), h("div", { key: '6bf71c54ee895db920d3f98ed7bc8869d9ea3874', class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { key: '2f9ebe8996ebfc8371116ec48ca4b750b8c42e43', name: "end" }), shouldRenderInnerIcon && this.renderSelectIcon()), shouldRenderOuterIcon && this.renderSelectIcon(), shouldRenderHighlight && h("div", { key: 'cdf1742886c636270ada0b1e5c61faff40bacdc1', class: "select-highlight" })), this.renderBottomContent()));
|
|
941
978
|
}
|
|
942
979
|
static get is() { return "ion-select"; }
|
|
943
980
|
static get encapsulation() { return "shadow"; }
|
|
@@ -1618,25 +1655,99 @@ const parseValue = (value) => {
|
|
|
1618
1655
|
}
|
|
1619
1656
|
return value.toString();
|
|
1620
1657
|
};
|
|
1621
|
-
const generateText = (opts, value, compareWith) => {
|
|
1658
|
+
const generateText = (opts, value, compareWith, useHTML = false) => {
|
|
1622
1659
|
if (value === undefined) {
|
|
1623
1660
|
return '';
|
|
1624
1661
|
}
|
|
1625
1662
|
if (Array.isArray(value)) {
|
|
1626
1663
|
return value
|
|
1627
|
-
.map((v) => textForValue(opts, v, compareWith))
|
|
1664
|
+
.map((v) => textForValue(opts, v, compareWith, useHTML))
|
|
1628
1665
|
.filter((opt) => opt !== null)
|
|
1629
1666
|
.join(', ');
|
|
1630
1667
|
}
|
|
1631
1668
|
else {
|
|
1632
|
-
return textForValue(opts, value, compareWith) || '';
|
|
1669
|
+
return textForValue(opts, value, compareWith, useHTML) || '';
|
|
1633
1670
|
}
|
|
1634
1671
|
};
|
|
1635
|
-
const textForValue = (opts, value, compareWith) => {
|
|
1672
|
+
const textForValue = (opts, value, compareWith, useHTML = false) => {
|
|
1636
1673
|
const selectOpt = opts.find((opt) => {
|
|
1637
1674
|
return compareOptions(value, getOptionValue(opt), compareWith);
|
|
1638
1675
|
});
|
|
1639
|
-
|
|
1676
|
+
const customHTMLEnabled = config.get('innerHTMLTemplatesEnabled', ENABLE_HTML_CONTENT_DEFAULT);
|
|
1677
|
+
if (!selectOpt)
|
|
1678
|
+
return null;
|
|
1679
|
+
return customHTMLEnabled && useHTML
|
|
1680
|
+
? getOptionContent(selectOpt, undefined, true)
|
|
1681
|
+
: selectOpt.textContent;
|
|
1682
|
+
};
|
|
1683
|
+
/**
|
|
1684
|
+
* Trims whitespace from all text nodes within a DOM tree.
|
|
1685
|
+
* This prevents invisible layout shifts and unwanted gaps between
|
|
1686
|
+
* elements when HTML content is injected via innerHTML or cloneNode,
|
|
1687
|
+
* as browsers preserve whitespace (tabs, newlines, spaces) from
|
|
1688
|
+
* the original source markup.
|
|
1689
|
+
*
|
|
1690
|
+
* @param node The root node to start trimming text nodes from.
|
|
1691
|
+
*/
|
|
1692
|
+
const trimTextNodes = (node) => {
|
|
1693
|
+
node.childNodes.forEach((child) => {
|
|
1694
|
+
var _a;
|
|
1695
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
1696
|
+
child.textContent = ((_a = child.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || '';
|
|
1697
|
+
}
|
|
1698
|
+
else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
1699
|
+
trimTextNodes(child);
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
};
|
|
1703
|
+
/**
|
|
1704
|
+
* Extracts and clones content from an `ion-select-option` element
|
|
1705
|
+
* for rendering within overlay interfaces or the select text when `customHTMLEnabled` is `true`.
|
|
1706
|
+
*
|
|
1707
|
+
* @param option - The `ion-select-option` element to extract content from.
|
|
1708
|
+
* @param slotName - Optional slot name to extract. If omitted, extracts the default slot content.
|
|
1709
|
+
* @param useHTML - If `true`, the returned string will include any custom HTML content. If `false`, the returned string will be plain text without any HTML.
|
|
1710
|
+
* @returns When `useHTML` is `true`, a sanitized HTML string. When `false`, a
|
|
1711
|
+
* div element containing cloned child nodes. Returns `null` if no matching
|
|
1712
|
+
* content is found.
|
|
1713
|
+
*/
|
|
1714
|
+
const getOptionContent = (option, slotName, useHTML = false) => {
|
|
1715
|
+
let nodes;
|
|
1716
|
+
if (slotName) {
|
|
1717
|
+
// Named slot: get elements with matching slot attribute
|
|
1718
|
+
nodes = Array.from(option.children).filter((el) => el.getAttribute('slot') === slotName);
|
|
1719
|
+
}
|
|
1720
|
+
else {
|
|
1721
|
+
// Default slot: get nodes without a slot attribute
|
|
1722
|
+
nodes = Array.from(option.childNodes).filter((node) => {
|
|
1723
|
+
var _a;
|
|
1724
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
1725
|
+
return !node.hasAttribute('slot');
|
|
1726
|
+
}
|
|
1727
|
+
// Exclude whitespace-only text nodes to prevent empty container returns
|
|
1728
|
+
return ((_a = node.textContent) === null || _a === void 0 ? void 0 : _a.trim().length) !== 0;
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
if (nodes.length === 0) {
|
|
1732
|
+
return null;
|
|
1733
|
+
}
|
|
1734
|
+
// Clone each node into a temporary container
|
|
1735
|
+
const container = document.createElement('div');
|
|
1736
|
+
nodes.forEach((n) => {
|
|
1737
|
+
var _a;
|
|
1738
|
+
const clone = n.cloneNode(true);
|
|
1739
|
+
if (clone.nodeType === Node.TEXT_NODE) {
|
|
1740
|
+
clone.textContent = ((_a = clone.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || '';
|
|
1741
|
+
}
|
|
1742
|
+
else {
|
|
1743
|
+
trimTextNodes(clone);
|
|
1744
|
+
}
|
|
1745
|
+
container.appendChild(clone);
|
|
1746
|
+
});
|
|
1747
|
+
if (useHTML) {
|
|
1748
|
+
return sanitizeDOMString(container.innerHTML.trim()) || null;
|
|
1749
|
+
}
|
|
1750
|
+
return container;
|
|
1640
1751
|
};
|
|
1641
1752
|
let selectIds = 0;
|
|
1642
1753
|
const OPTION_CLASS = 'select-interface-option';
|
|
@@ -103,6 +103,13 @@
|
|
|
103
103
|
* @prop --border-width: Width of the select border
|
|
104
104
|
*
|
|
105
105
|
* @prop --ripple-color: The color of the ripple effect on MD mode.
|
|
106
|
+
*
|
|
107
|
+
* @prop --select-text-media-width: The width of media (icons/images) in the select text.
|
|
108
|
+
* @prop --select-text-media-height: The height of media (icons/images) in the select text.
|
|
109
|
+
* @prop --select-text-media-border-width: The border width of media (icons/images) in the select text.
|
|
110
|
+
* @prop --select-text-media-border-color: The border color of media (icons/images) in the select text.
|
|
111
|
+
* @prop --select-text-media-border-radius: The border radius of media (icons/images) in the select text.
|
|
112
|
+
* @prop --select-text-media-border-style: The border style of media (icons/images) in the select text.
|
|
106
113
|
*/
|
|
107
114
|
--padding-top: 0px;
|
|
108
115
|
--padding-end: 0px;
|
|
@@ -114,6 +121,8 @@
|
|
|
114
121
|
--highlight-color-focused: var(--ion-color-primary, #0054e9);
|
|
115
122
|
--highlight-color-valid: var(--ion-color-success, #2dd55b);
|
|
116
123
|
--highlight-color-invalid: var(--ion-color-danger, #c5000f);
|
|
124
|
+
--select-text-media-height: 1.5em;
|
|
125
|
+
--select-text-media-width: 1.5em;
|
|
117
126
|
/**
|
|
118
127
|
* This is a private API that is used to switch
|
|
119
128
|
* out the highlight color based on the state
|
|
@@ -218,6 +227,19 @@ button {
|
|
|
218
227
|
overflow: hidden;
|
|
219
228
|
}
|
|
220
229
|
|
|
230
|
+
.select-text img,
|
|
231
|
+
.select-text ion-img,
|
|
232
|
+
.select-text ion-icon,
|
|
233
|
+
.select-text ion-thumbnail,
|
|
234
|
+
.select-text ion-avatar {
|
|
235
|
+
border-radius: var(--select-text-media-border-radius);
|
|
236
|
+
width: var(--select-text-media-width);
|
|
237
|
+
height: var(--select-text-media-height);
|
|
238
|
+
border-width: var(--select-text-media-border-width);
|
|
239
|
+
border-style: var(--select-text-media-border-style);
|
|
240
|
+
border-color: var(--select-text-media-border-color);
|
|
241
|
+
}
|
|
242
|
+
|
|
221
243
|
.select-wrapper {
|
|
222
244
|
display: flex;
|
|
223
245
|
position: relative;
|
|
@@ -619,6 +641,15 @@ button {
|
|
|
619
641
|
min-width: 16px;
|
|
620
642
|
}
|
|
621
643
|
|
|
644
|
+
/**
|
|
645
|
+
* If the select text contains rich content, we want to add some
|
|
646
|
+
* spacing between the items without changing the display to prevent
|
|
647
|
+
* losing the ellipsis behavior.
|
|
648
|
+
*/
|
|
649
|
+
.select-text > * {
|
|
650
|
+
margin-inline-start: 8px;
|
|
651
|
+
}
|
|
652
|
+
|
|
622
653
|
.label-text-wrapper {
|
|
623
654
|
/**
|
|
624
655
|
* Label text should not extend
|
|
@@ -63,6 +63,15 @@
|
|
|
63
63
|
height: 100%;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
.select-option-label {
|
|
67
|
+
display: flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.select-option-description {
|
|
72
|
+
display: block;
|
|
73
|
+
}
|
|
74
|
+
|
|
66
75
|
:host(.in-modal-default) ion-content::part(scroll) {
|
|
67
76
|
--padding-bottom: var(--token-space-400, var(--token-scale-400, 16px));
|
|
68
77
|
}
|
|
@@ -127,4 +136,19 @@ ion-content {
|
|
|
127
136
|
}
|
|
128
137
|
ion-content:has(.radio-checked) .ion-focused:not(.item-radio-checked) {
|
|
129
138
|
--background-focused-opacity: 1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.select-option-label {
|
|
142
|
+
gap: var(--token-space-300, var(--token-scale-300, 12px));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.select-option-description {
|
|
146
|
+
font-size: var(--token-font-size-350, 0.875rem);
|
|
147
|
+
font-weight: var(--token-font-weight-regular, 400);
|
|
148
|
+
letter-spacing: var(--token-font-letter-spacing-0, 0%);
|
|
149
|
+
line-height: var(--token-font-line-height-600, var(--token-scale-600, 24px));
|
|
150
|
+
text-decoration: none;
|
|
151
|
+
text-transform: none;
|
|
152
|
+
color: var(--token-text-subtle, var(--token-primitives-neutral-1000, #3b3b3b));
|
|
153
|
+
font-size: var(--token-font-size-350, 0.875rem);
|
|
130
154
|
}
|
|
@@ -1,7 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A heuristic that applies CSS to tablet
|
|
3
|
+
* viewports.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* @include tablet-viewport() {
|
|
7
|
+
* :host {
|
|
8
|
+
* background-color: green;
|
|
9
|
+
* }
|
|
10
|
+
* }
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* A heuristic that applies CSS to mobile
|
|
14
|
+
* viewports (i.e. phones, not tablets).
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* @include mobile-viewport() {
|
|
18
|
+
* :host {
|
|
19
|
+
* background-color: blue;
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Convert a font size to a dynamic font size.
|
|
25
|
+
* Fonts that participate in Dynamic Type should use
|
|
26
|
+
* dynamic font sizes.
|
|
27
|
+
* @param size - The initial font size including the unit (i.e. px or pt)
|
|
28
|
+
* @param unit (optional) - The unit to convert to. Use this if you want to
|
|
29
|
+
* convert to a unit other than $baselineUnit.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Convert a font size to a dynamic font size but impose
|
|
33
|
+
* a maximum font size.
|
|
34
|
+
* @param size - The initial font size including the unit (i.e. px or pt)
|
|
35
|
+
* @param maxScale - The maximum scale of the font (i.e. 2.5 for a maximum 250% scale).
|
|
36
|
+
* @param unit (optional) - The unit to convert the initial font size to. Use this if you want to
|
|
37
|
+
* convert to a unit other than $baselineUnit.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Convert a font size to a dynamic font size but impose
|
|
41
|
+
* a minimum font size.
|
|
42
|
+
* @param size - The initial font size including the unit (i.e. px or pt)
|
|
43
|
+
* @param minScale - The minimum scale of the font (i.e. 0.8 for a minimum 80% scale).
|
|
44
|
+
* @param unit (optional) - The unit to convert the initial font size to. Use this if you want to
|
|
45
|
+
* convert to a unit other than $baselineUnit.
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* Convert a font size to a dynamic font size but impose
|
|
49
|
+
* maximum and minimum font sizes.
|
|
50
|
+
* @param size - The initial font size including the unit (i.e. px or pt)
|
|
51
|
+
* @param minScale - The minimum scale of the font (i.e. 0.8 for a minimum 80% scale).
|
|
52
|
+
* @param maxScale - The maximum scale of the font (i.e. 2.5 for a maximum 250% scale).
|
|
53
|
+
* @param unit (optional) - The unit to convert the initial font size to. Use this if you want to
|
|
54
|
+
* convert to a unit other than $baselineUnit.
|
|
55
|
+
*/
|
|
1
56
|
:host {
|
|
2
57
|
height: 100%;
|
|
3
58
|
}
|
|
4
59
|
|
|
60
|
+
.select-option-label {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.select-option-description {
|
|
66
|
+
display: block;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.select-option-label {
|
|
70
|
+
gap: 12px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.select-option-description {
|
|
74
|
+
padding-left: 0;
|
|
75
|
+
padding-right: 0;
|
|
76
|
+
padding-top: 5px;
|
|
77
|
+
padding-bottom: 0;
|
|
78
|
+
color: var(--ion-color-step-700, var(--ion-text-color-step-300, #4d4d4d));
|
|
79
|
+
font-size: 0.75rem;
|
|
80
|
+
}
|
|
81
|
+
|
|
5
82
|
/**
|
|
6
83
|
* Convert a pixels given value into rem
|
|
7
84
|
*
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { getIonMode } from "../../global/ionic-global";
|
|
5
5
|
import { Host, forceUpdate, h } from "@stencil/core";
|
|
6
6
|
import { safeCall } from "../../utils/overlays";
|
|
7
|
+
import { renderOptionLabel } from "../../utils/select-option-render";
|
|
7
8
|
import { getClassMap, hostContext } from "../../utils/theme";
|
|
8
9
|
export class SelectModal {
|
|
9
10
|
constructor() {
|
|
@@ -60,33 +61,65 @@ export class SelectModal {
|
|
|
60
61
|
}
|
|
61
62
|
renderRadioOptions() {
|
|
62
63
|
const checked = this.options.filter((o) => o.checked).map((o) => o.value)[0];
|
|
63
|
-
return (h("ion-radio-group", { value: checked, onIonChange: (ev) => this.callOptionHandler(ev) }, this.options.map((option) =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
64
|
+
return (h("ion-radio-group", { value: checked, onIonChange: (ev) => this.callOptionHandler(ev) }, this.options.map((option, index) => {
|
|
65
|
+
/**
|
|
66
|
+
* Cast to `SelectOverlayOption` to access rich content
|
|
67
|
+
* fields (`startContent`, `endContent`, `description`)
|
|
68
|
+
* that are passed through from `ion-select` but not
|
|
69
|
+
* part of the public `SelectModalOption` interface.
|
|
70
|
+
*/
|
|
71
|
+
const richOption = option;
|
|
72
|
+
const optionLabelOptions = {
|
|
73
|
+
id: `modal-option-${index}`,
|
|
74
|
+
label: richOption.text,
|
|
75
|
+
startContent: richOption.startContent,
|
|
76
|
+
endContent: richOption.endContent,
|
|
77
|
+
description: richOption.description,
|
|
78
|
+
};
|
|
79
|
+
return (h("ion-item", { lines: "none", class: Object.assign({
|
|
80
|
+
// TODO FW-4784
|
|
81
|
+
'item-radio-checked': option.value === checked
|
|
82
|
+
}, getClassMap(option.cssClass)) }, h("ion-radio", { value: option.value, disabled: option.disabled, justify: "start", labelPlacement: "end", onClick: () => this.closeModal(), onKeyUp: (ev) => {
|
|
83
|
+
if (ev.key === ' ') {
|
|
84
|
+
/**
|
|
85
|
+
* Selecting a radio option with keyboard navigation,
|
|
86
|
+
* either through the Enter or Space keys, should
|
|
87
|
+
* dismiss the modal.
|
|
88
|
+
*/
|
|
89
|
+
this.closeModal();
|
|
90
|
+
}
|
|
91
|
+
} }, renderOptionLabel(optionLabelOptions, 'select-option-label'))));
|
|
92
|
+
})));
|
|
76
93
|
}
|
|
77
94
|
renderCheckboxOptions() {
|
|
78
|
-
return this.options.map((option) =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
95
|
+
return this.options.map((option, index) => {
|
|
96
|
+
/**
|
|
97
|
+
* Cast to `SelectOverlayOption` to access rich content
|
|
98
|
+
* fields (`startContent`, `endContent`, `description`)
|
|
99
|
+
* that are passed through from `ion-select` but not
|
|
100
|
+
* part of the public `SelectModalOption` interface.
|
|
101
|
+
*/
|
|
102
|
+
const richOption = option;
|
|
103
|
+
const optionLabelOptions = {
|
|
104
|
+
id: `modal-option-${index}`,
|
|
105
|
+
label: richOption.text,
|
|
106
|
+
startContent: richOption.startContent,
|
|
107
|
+
endContent: richOption.endContent,
|
|
108
|
+
description: richOption.description,
|
|
109
|
+
};
|
|
110
|
+
return (h("ion-item", { class: Object.assign({
|
|
111
|
+
// TODO FW-4784
|
|
112
|
+
'item-checkbox-checked': option.checked
|
|
113
|
+
}, getClassMap(option.cssClass)) }, h("ion-checkbox", { value: option.value, disabled: option.disabled, checked: option.checked, justify: "start", labelPlacement: "end", onIonChange: (ev) => {
|
|
114
|
+
this.setChecked(ev);
|
|
115
|
+
this.callOptionHandler(ev);
|
|
116
|
+
// TODO FW-4784
|
|
117
|
+
forceUpdate(this);
|
|
118
|
+
} }, renderOptionLabel(optionLabelOptions, 'select-option-label'))));
|
|
119
|
+
});
|
|
87
120
|
}
|
|
88
121
|
render() {
|
|
89
|
-
return (h(Host, { key: '
|
|
122
|
+
return (h(Host, { key: '3fa77e7726ef4b7678cc2ea910e2e09b03ef7cbe', class: Object.assign({ [getIonMode(this)]: true }, this.getModalContextClasses()) }, h("ion-header", { key: '141c87f6f2c0de5e3b614fb616402d2e26276829' }, h("ion-toolbar", { key: '3a44e1c42eb4b48e313a234540ad4f71a1fe7d05' }, this.header !== undefined && h("ion-title", { key: '418d57b29f25eb88a5a1cd9ccbdf73f34f75408a' }, this.header), h("ion-buttons", { key: '7e37ad754da093d86acb758583acf47b9960fba1', slot: "end" }, h("ion-button", { key: '5ac2c7d9bbc345f575319c141f0f6d7acdc4921e', onClick: () => this.closeModal() }, this.cancelText)))), h("ion-content", { key: '4d397a2f668220839e58611da686a2eed94d4135' }, h("ion-list", { key: 'e3e870e4a060af0d475ba8b8263b0eeb4158bac8' }, this.multiple === true ? this.renderCheckboxOptions() : this.renderRadioOptions()))));
|
|
90
123
|
}
|
|
91
124
|
static get is() { return "ion-select-modal"; }
|
|
92
125
|
static get encapsulation() { return "scoped"; }
|
|
@@ -1,6 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A heuristic that applies CSS to tablet
|
|
3
|
+
* viewports.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* @include tablet-viewport() {
|
|
7
|
+
* :host {
|
|
8
|
+
* background-color: green;
|
|
9
|
+
* }
|
|
10
|
+
* }
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* A heuristic that applies CSS to mobile
|
|
14
|
+
* viewports (i.e. phones, not tablets).
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* @include mobile-viewport() {
|
|
18
|
+
* :host {
|
|
19
|
+
* background-color: blue;
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Convert a font size to a dynamic font size.
|
|
25
|
+
* Fonts that participate in Dynamic Type should use
|
|
26
|
+
* dynamic font sizes.
|
|
27
|
+
* @param size - The initial font size including the unit (i.e. px or pt)
|
|
28
|
+
* @param unit (optional) - The unit to convert to. Use this if you want to
|
|
29
|
+
* convert to a unit other than $baselineUnit.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Convert a font size to a dynamic font size but impose
|
|
33
|
+
* a maximum font size.
|
|
34
|
+
* @param size - The initial font size including the unit (i.e. px or pt)
|
|
35
|
+
* @param maxScale - The maximum scale of the font (i.e. 2.5 for a maximum 250% scale).
|
|
36
|
+
* @param unit (optional) - The unit to convert the initial font size to. Use this if you want to
|
|
37
|
+
* convert to a unit other than $baselineUnit.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Convert a font size to a dynamic font size but impose
|
|
41
|
+
* a minimum font size.
|
|
42
|
+
* @param size - The initial font size including the unit (i.e. px or pt)
|
|
43
|
+
* @param minScale - The minimum scale of the font (i.e. 0.8 for a minimum 80% scale).
|
|
44
|
+
* @param unit (optional) - The unit to convert the initial font size to. Use this if you want to
|
|
45
|
+
* convert to a unit other than $baselineUnit.
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* Convert a font size to a dynamic font size but impose
|
|
49
|
+
* maximum and minimum font sizes.
|
|
50
|
+
* @param size - The initial font size including the unit (i.e. px or pt)
|
|
51
|
+
* @param minScale - The minimum scale of the font (i.e. 0.8 for a minimum 80% scale).
|
|
52
|
+
* @param maxScale - The maximum scale of the font (i.e. 2.5 for a maximum 250% scale).
|
|
53
|
+
* @param unit (optional) - The unit to convert the initial font size to. Use this if you want to
|
|
54
|
+
* convert to a unit other than $baselineUnit.
|
|
55
|
+
*/
|
|
1
56
|
:host {
|
|
2
57
|
height: 100%;
|
|
3
|
-
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.select-option-label {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.select-option-description {
|
|
66
|
+
display: block;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.select-option-label {
|
|
70
|
+
gap: 12px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.select-option-description {
|
|
74
|
+
padding-left: 0;
|
|
75
|
+
padding-right: 0;
|
|
76
|
+
padding-top: 5px;
|
|
77
|
+
padding-bottom: 0;
|
|
78
|
+
color: var(--ion-color-step-700, var(--ion-text-color-step-300, #4d4d4d));
|
|
79
|
+
font-size: 0.75rem;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
4
83
|
* A heuristic that applies CSS to tablet
|
|
5
84
|
* viewports.
|
|
6
85
|
*
|