@ionic/core 7.5.4-dev.11699895923.130c21aa → 7.5.4-dev.11701112913.1ea61220
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/input.utils.js +3 -2
- package/components/ion-input.js +29 -8
- package/components/ion-select.js +41 -6
- package/components/ion-textarea.js +28 -7
- package/dist/cjs/{input.utils-590f0f18.js → input.utils-bdb6953c.js} +3 -2
- package/dist/cjs/ion-input.cjs.entry.js +30 -9
- package/dist/cjs/ion-select_3.cjs.entry.js +41 -6
- package/dist/cjs/ion-textarea.cjs.entry.js +29 -8
- package/dist/collection/components/input/input.ios.css +10 -3
- package/dist/collection/components/input/input.js +29 -6
- package/dist/collection/components/input/input.md.css +13 -12
- package/dist/collection/components/input/test/slot/input.e2e.js +51 -0
- package/dist/collection/components/select/select.ios.css +56 -7
- package/dist/collection/components/select/select.js +41 -4
- package/dist/collection/components/select/select.md.css +59 -19
- package/dist/collection/components/select/test/label/select.e2e.js +1 -1
- package/dist/collection/components/select/test/slot/select.e2e.js +67 -0
- package/dist/collection/components/textarea/test/slot/textarea.e2e.js +51 -0
- package/dist/collection/components/textarea/textarea.ios.css +37 -5
- package/dist/collection/components/textarea/textarea.js +28 -5
- package/dist/collection/components/textarea/textarea.md.css +47 -14
- package/dist/collection/utils/slot-mutation-controller.js +3 -2
- package/dist/docs.json +49 -1
- package/dist/esm/{input.utils-d04a7212.js → input.utils-2cf5e372.js} +3 -2
- package/dist/esm/ion-input.entry.js +30 -9
- package/dist/esm/ion-select_3.entry.js +41 -6
- package/dist/esm/ion-textarea.entry.js +29 -8
- package/dist/esm-es5/input.utils-2cf5e372.js +4 -0
- package/dist/esm-es5/ion-input.entry.js +1 -1
- package/dist/esm-es5/ion-select_3.entry.js +1 -1
- package/dist/esm-es5/ion-textarea.entry.js +1 -1
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-02663394.system.js +1 -1
- package/dist/ionic/p-0fa05d7a.system.entry.js +4 -0
- package/dist/ionic/p-33c650c5.entry.js +4 -0
- package/dist/ionic/p-37afecd5.entry.js +4 -0
- package/dist/ionic/p-79841aa8.entry.js +4 -0
- package/dist/ionic/p-7b44e419.js +4 -0
- package/dist/ionic/p-ad6ed7ab.system.entry.js +4 -0
- package/dist/ionic/p-c700acef.system.js +4 -0
- package/dist/ionic/p-cd73cfd1.system.entry.js +4 -0
- package/dist/types/components/input/input.d.ts +2 -0
- package/dist/types/components/select/select.d.ts +2 -0
- package/dist/types/components/textarea/textarea.d.ts +2 -0
- package/dist/types/components/toast/toast-interface.d.ts +1 -0
- package/dist/types/utils/slot-mutation-controller.d.ts +2 -2
- package/hydrate/index.js +107 -23
- package/package.json +1 -1
- package/dist/esm-es5/input.utils-d04a7212.js +0 -4
- package/dist/ionic/p-0becf7b5.system.entry.js +0 -4
- package/dist/ionic/p-1667596c.js +0 -4
- package/dist/ionic/p-265fec8a.entry.js +0 -4
- package/dist/ionic/p-2ae9a434.system.entry.js +0 -4
- package/dist/ionic/p-7def197f.system.js +0 -4
- package/dist/ionic/p-9b34caa0.system.entry.js +0 -4
- package/dist/ionic/p-e119ac8b.entry.js +0 -4
- package/dist/ionic/p-fa67d712.entry.js +0 -4
|
@@ -328,6 +328,7 @@
|
|
|
328
328
|
display: flex;
|
|
329
329
|
position: relative;
|
|
330
330
|
flex-grow: 1;
|
|
331
|
+
align-items: center;
|
|
331
332
|
width: 100%;
|
|
332
333
|
}
|
|
333
334
|
|
|
@@ -632,9 +633,7 @@
|
|
|
632
633
|
/**
|
|
633
634
|
* This makes the label sit above the input.
|
|
634
635
|
*/
|
|
635
|
-
:host(.
|
|
636
|
-
:host(.has-focus.input-label-placement-floating) .label-text-wrapper,
|
|
637
|
-
:host(.has-value.input-label-placement-floating) .label-text-wrapper {
|
|
636
|
+
:host(.label-floating) .label-text-wrapper {
|
|
638
637
|
transform: translateY(50%) scale(0.75);
|
|
639
638
|
/**
|
|
640
639
|
* Label text should not extend
|
|
@@ -643,6 +642,14 @@
|
|
|
643
642
|
max-width: calc(100% / 0.75);
|
|
644
643
|
}
|
|
645
644
|
|
|
645
|
+
::slotted([slot=start]) {
|
|
646
|
+
margin-inline-end: 16px;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
::slotted([slot=end]) {
|
|
650
|
+
margin-inline-start: 16px;
|
|
651
|
+
}
|
|
652
|
+
|
|
646
653
|
/**
|
|
647
654
|
* Convert a font size to a dynamic font size.
|
|
648
655
|
* Fonts that participate in Dynamic Type should use
|
|
@@ -819,9 +826,7 @@
|
|
|
819
826
|
}
|
|
820
827
|
}
|
|
821
828
|
|
|
822
|
-
:host(.input-fill-solid.input-label-placement-
|
|
823
|
-
:host(.has-focus.input-fill-solid.input-label-placement-floating) .label-text-wrapper,
|
|
824
|
-
:host(.has-value.input-fill-solid.input-label-placement-floating) .label-text-wrapper {
|
|
829
|
+
:host(.label-floating.input-fill-solid.input-label-placement-floating) .label-text-wrapper {
|
|
825
830
|
/**
|
|
826
831
|
* Label text should not extend
|
|
827
832
|
* beyond the bounds of the input.
|
|
@@ -953,9 +958,7 @@
|
|
|
953
958
|
/**
|
|
954
959
|
* This makes the label sit above the input.
|
|
955
960
|
*/
|
|
956
|
-
:host(.
|
|
957
|
-
:host(.has-value.input-fill-outline.input-label-placement-floating) .label-text-wrapper,
|
|
958
|
-
:host(.input-fill-outline.input-label-placement-stacked) .label-text-wrapper {
|
|
961
|
+
:host(.label-floating.input-fill-outline) .label-text-wrapper {
|
|
959
962
|
transform: translateY(-32%) scale(0.75);
|
|
960
963
|
margin-left: 0;
|
|
961
964
|
margin-right: 0;
|
|
@@ -1116,9 +1119,7 @@
|
|
|
1116
1119
|
* the floating/stacked label. We simulate this "cut out"
|
|
1117
1120
|
* by removing the top border from the notch fragment.
|
|
1118
1121
|
*/
|
|
1119
|
-
:host(.
|
|
1120
|
-
:host(.has-value.input-fill-outline.input-label-placement-floating) .input-outline-notch,
|
|
1121
|
-
:host(.input-fill-outline.input-label-placement-stacked) .input-outline-notch {
|
|
1122
|
+
:host(.label-floating.input-fill-outline) .input-outline-notch {
|
|
1122
1123
|
border-top: none;
|
|
1123
1124
|
}
|
|
1124
1125
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
|
+
*/
|
|
4
|
+
import { expect } from "@playwright/test";
|
|
5
|
+
import { configs, test } from "../../../../utils/test/playwright/index";
|
|
6
|
+
configs().forEach(({ title, screenshot, config }) => {
|
|
7
|
+
test.describe(title('input: start and end slots (visual checks)'), () => {
|
|
8
|
+
test('should not have visual regressions with a start-positioned label', async ({ page }) => {
|
|
9
|
+
await page.setContent(`
|
|
10
|
+
<ion-input label-placement="start" fill="solid" value="100" label="Weight" clear-input="true">
|
|
11
|
+
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
|
|
12
|
+
<ion-label slot="end">lbs</ion-label>
|
|
13
|
+
</ion-input>
|
|
14
|
+
`, config);
|
|
15
|
+
const input = page.locator('ion-input');
|
|
16
|
+
await expect(input).toHaveScreenshot(screenshot(`input-slots-label-start`));
|
|
17
|
+
});
|
|
18
|
+
test('should not have visual regressions with a floating label', async ({ page }) => {
|
|
19
|
+
await page.setContent(`
|
|
20
|
+
<ion-input label-placement="floating" fill="solid" value="100" label="Weight" clear-input="true">
|
|
21
|
+
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
|
|
22
|
+
<ion-label slot="end">lbs</ion-label>
|
|
23
|
+
</ion-input>
|
|
24
|
+
`, config);
|
|
25
|
+
const input = page.locator('ion-input');
|
|
26
|
+
await expect(input).toHaveScreenshot(screenshot(`input-slots-label-floating`));
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
|
31
|
+
test.describe(title('input: start and end slots (functionality checks)'), () => {
|
|
32
|
+
test('should raise floating label when there is content in the start slot', async ({ page }) => {
|
|
33
|
+
await page.setContent(`
|
|
34
|
+
<ion-input label-placement="floating" fill="solid" label="Weight">
|
|
35
|
+
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
|
|
36
|
+
</ion-input>
|
|
37
|
+
`, config);
|
|
38
|
+
const input = page.locator('ion-input');
|
|
39
|
+
await expect(input).toHaveClass(/label-floating/);
|
|
40
|
+
});
|
|
41
|
+
test('should raise floating label when there is content in the end slot', async ({ page }) => {
|
|
42
|
+
await page.setContent(`
|
|
43
|
+
<ion-input label-placement="floating" fill="solid" label="Weight">
|
|
44
|
+
<ion-icon slot="end" name="barbell" aria-hidden="true"></ion-icon>
|
|
45
|
+
</ion-input>
|
|
46
|
+
`, config);
|
|
47
|
+
const input = page.locator('ion-input');
|
|
48
|
+
await expect(input).toHaveClass(/label-floating/);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -215,6 +215,12 @@ button {
|
|
|
215
215
|
margin-top: 0;
|
|
216
216
|
margin-bottom: 0;
|
|
217
217
|
position: relative;
|
|
218
|
+
/**
|
|
219
|
+
* Prevent the icon from shrinking when the label and/or
|
|
220
|
+
* selected item text is long enough to fill the rest of
|
|
221
|
+
* the container.
|
|
222
|
+
*/
|
|
223
|
+
flex-shrink: 0;
|
|
218
224
|
}
|
|
219
225
|
|
|
220
226
|
/**
|
|
@@ -298,6 +304,23 @@ button {
|
|
|
298
304
|
transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
299
305
|
}
|
|
300
306
|
|
|
307
|
+
.select-wrapper-inner {
|
|
308
|
+
display: flex;
|
|
309
|
+
align-items: center;
|
|
310
|
+
overflow: hidden;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
:host(.select-label-placement-stacked) .select-wrapper-inner,
|
|
314
|
+
:host(.select-label-placement-floating) .select-wrapper-inner {
|
|
315
|
+
/**
|
|
316
|
+
* When using a stacked/floating label, the inner wrapper is
|
|
317
|
+
* stacked vertically under the label container. This line
|
|
318
|
+
* ensures that the inner wrapper fills all the remaining height
|
|
319
|
+
* of the component.
|
|
320
|
+
*/
|
|
321
|
+
flex-grow: 1;
|
|
322
|
+
}
|
|
323
|
+
|
|
301
324
|
:host(.ion-touched.ion-invalid) {
|
|
302
325
|
--highlight-color: var(--highlight-color-invalid);
|
|
303
326
|
}
|
|
@@ -538,11 +561,23 @@ button {
|
|
|
538
561
|
* The placeholder should be hidden when the label
|
|
539
562
|
* is on top of the select. This prevents the label
|
|
540
563
|
* from overlapping any placeholder value.
|
|
564
|
+
*
|
|
565
|
+
* TODO(FW-5592): Remove :not(.label-floating) piece
|
|
541
566
|
*/
|
|
542
|
-
:host(.select-label-placement-floating) .native-wrapper .select-placeholder {
|
|
567
|
+
:host(.select-label-placement-floating:not(.label-floating)) .native-wrapper .select-placeholder {
|
|
543
568
|
opacity: 0;
|
|
544
569
|
}
|
|
545
570
|
|
|
571
|
+
/**
|
|
572
|
+
* We don't use .label-floating here because that would
|
|
573
|
+
* also include the case where the label is floating due
|
|
574
|
+
* to content in the start/end slot. We want the opacity
|
|
575
|
+
* to remain at the default in this case, since the select
|
|
576
|
+
* isn't being actively interacted with.
|
|
577
|
+
*
|
|
578
|
+
* TODO(FW-5592): Change entire selector to:
|
|
579
|
+
* :host(.label-floating.select-label-placement-floating) .native-wrapper .select-placeholder
|
|
580
|
+
*/
|
|
546
581
|
:host(.select-expanded.select-label-placement-floating) .native-wrapper .select-placeholder,
|
|
547
582
|
:host(.ion-focused.select-label-placement-floating) .native-wrapper .select-placeholder,
|
|
548
583
|
:host(.has-value.select-label-placement-floating) .native-wrapper .select-placeholder {
|
|
@@ -552,10 +587,7 @@ button {
|
|
|
552
587
|
/**
|
|
553
588
|
* This makes the label sit above the input.
|
|
554
589
|
*/
|
|
555
|
-
:host(.
|
|
556
|
-
:host(.select-expanded.select-label-placement-floating) .label-text-wrapper,
|
|
557
|
-
:host(.ion-focused.select-label-placement-floating) .label-text-wrapper,
|
|
558
|
-
:host(.has-value.select-label-placement-floating) .label-text-wrapper {
|
|
590
|
+
:host(.label-floating) .label-text-wrapper {
|
|
559
591
|
transform: translateY(50%) scale(0.75);
|
|
560
592
|
/**
|
|
561
593
|
* Label text should not extend
|
|
@@ -564,6 +596,23 @@ button {
|
|
|
564
596
|
max-width: calc(100% / 0.75);
|
|
565
597
|
}
|
|
566
598
|
|
|
599
|
+
::slotted([slot=start]), ::slotted([slot=end]) {
|
|
600
|
+
/**
|
|
601
|
+
* Prevent the slots from shrinking when the label and/or
|
|
602
|
+
* selected item text is long enough to fill the rest of
|
|
603
|
+
* the container.
|
|
604
|
+
*/
|
|
605
|
+
flex-shrink: 0;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
::slotted([slot=start]) {
|
|
609
|
+
margin-inline-end: 16px;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
::slotted([slot=end]) {
|
|
613
|
+
margin-inline-start: 16px;
|
|
614
|
+
}
|
|
615
|
+
|
|
567
616
|
/**
|
|
568
617
|
* Convert a font size to a dynamic font size.
|
|
569
618
|
* Fonts that participate in Dynamic Type should use
|
|
@@ -643,8 +692,8 @@ button {
|
|
|
643
692
|
color: var(--ion-color-step-650, #595959);
|
|
644
693
|
}
|
|
645
694
|
|
|
646
|
-
:host(.select-label-placement-stacked) .
|
|
647
|
-
:host(.select-label-placement-floating) .
|
|
695
|
+
:host(.select-label-placement-stacked) .select-wrapper-inner,
|
|
696
|
+
:host(.select-label-placement-floating) .select-wrapper-inner {
|
|
648
697
|
width: calc(100% - 1.125rem - 4px);
|
|
649
698
|
}
|
|
650
699
|
|
|
@@ -16,6 +16,8 @@ import { getIonMode } from "../../global/ionic-global";
|
|
|
16
16
|
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
|
17
17
|
*
|
|
18
18
|
* @slot label - The label text to associate with the select. Use the `labelPlacement` property to control where the label is placed relative to the select. Use this if you need to render a label with custom HTML.
|
|
19
|
+
* @slot start - Content to display at the leading edge of the select.
|
|
20
|
+
* @slot end - Content to display at the trailing edge of the select.
|
|
19
21
|
*
|
|
20
22
|
* @part placeholder - The text displayed in the select when there is no value.
|
|
21
23
|
* @part text - The displayed value of the select.
|
|
@@ -30,8 +32,22 @@ export class Select {
|
|
|
30
32
|
// This flag ensures we log the deprecation warning at most once.
|
|
31
33
|
this.hasLoggedDeprecationWarning = false;
|
|
32
34
|
this.onClick = (ev) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
const target = ev.target;
|
|
36
|
+
const closestSlot = target.closest('[slot="start"], [slot="end"]');
|
|
37
|
+
if (target === this.el || closestSlot === null) {
|
|
38
|
+
this.setFocus();
|
|
39
|
+
this.open(ev);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
/**
|
|
43
|
+
* Prevent clicks to the start/end slots from opening the select.
|
|
44
|
+
* We ensure the target isn't this element in case the select is slotted
|
|
45
|
+
* in, for example, an item. This would prevent the select from ever
|
|
46
|
+
* being opened since the element itself has slot="start"/"end".
|
|
47
|
+
*/
|
|
48
|
+
ev.stopPropagation();
|
|
49
|
+
ev.preventDefault();
|
|
50
|
+
}
|
|
35
51
|
};
|
|
36
52
|
this.onFocus = () => {
|
|
37
53
|
this.ionFocus.emit();
|
|
@@ -534,7 +550,27 @@ export class Select {
|
|
|
534
550
|
const rtl = isRTL(el) ? 'rtl' : 'ltr';
|
|
535
551
|
const inItem = hostContext('ion-item', this.el);
|
|
536
552
|
const shouldRenderHighlight = mode === 'md' && fill !== 'outline' && !inItem;
|
|
553
|
+
const hasValue = this.hasValue();
|
|
554
|
+
const hasStartEndSlots = el.querySelector('[slot="start"], [slot="end"]') !== null;
|
|
537
555
|
renderHiddenInput(true, el, name, parseValue(value), disabled);
|
|
556
|
+
/**
|
|
557
|
+
* If the label is stacked, it should always sit above the select.
|
|
558
|
+
* For floating labels, the label should move above the select if
|
|
559
|
+
* the select has a value, is open, or has anything in either
|
|
560
|
+
* the start or end slot.
|
|
561
|
+
*
|
|
562
|
+
* If there is content in the start slot, the label would overlap
|
|
563
|
+
* it if not forced to float. This is also applied to the end slot
|
|
564
|
+
* because with the default or solid fills, the select is not
|
|
565
|
+
* vertically centered in the container, but the label is. This
|
|
566
|
+
* causes the slots and label to appear vertically offset from each
|
|
567
|
+
* other when the label isn't floating above the input. This doesn't
|
|
568
|
+
* apply to the outline fill, but this was not accounted for to keep
|
|
569
|
+
* things consistent.
|
|
570
|
+
*
|
|
571
|
+
* TODO(FW-5592): Remove hasStartEndSlots condition
|
|
572
|
+
*/
|
|
573
|
+
const labelShouldFloat = labelPlacement === 'stacked' || (labelPlacement === 'floating' && (hasValue || isExpanded || hasStartEndSlots));
|
|
538
574
|
return (h(Host, { onClick: this.onClick, class: createColorClasses(this.color, {
|
|
539
575
|
[mode]: true,
|
|
540
576
|
'in-item': inItem,
|
|
@@ -542,7 +578,8 @@ export class Select {
|
|
|
542
578
|
'select-disabled': disabled,
|
|
543
579
|
'select-expanded': isExpanded,
|
|
544
580
|
'has-expanded-icon': expandedIcon !== undefined,
|
|
545
|
-
'has-value':
|
|
581
|
+
'has-value': hasValue,
|
|
582
|
+
'label-floating': labelShouldFloat,
|
|
546
583
|
'has-placeholder': placeholder !== undefined,
|
|
547
584
|
'ion-focusable': true,
|
|
548
585
|
[`select-${rtl}`]: true,
|
|
@@ -550,7 +587,7 @@ export class Select {
|
|
|
550
587
|
[`select-justify-${justify}`]: justifyEnabled,
|
|
551
588
|
[`select-shape-${shape}`]: shape !== undefined,
|
|
552
589
|
[`select-label-placement-${labelPlacement}`]: true,
|
|
553
|
-
}) }, h("label", { class: "select-wrapper", id: "select-label" }, this.renderLabelContainer(), h("div", { class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), !hasFloatingOrStackedLabel && this.renderSelectIcon()
|
|
590
|
+
}) }, h("label", { class: "select-wrapper", id: "select-label" }, this.renderLabelContainer(), h("div", { class: "select-wrapper-inner" }, h("slot", { name: "start" }), h("div", { class: "native-wrapper", ref: (el) => (this.nativeWrapperEl = el), part: "container" }, this.renderSelectText(), this.renderListbox()), h("slot", { name: "end" }), !hasFloatingOrStackedLabel && this.renderSelectIcon()), hasFloatingOrStackedLabel && this.renderSelectIcon(), shouldRenderHighlight && h("div", { class: "select-highlight" }))));
|
|
554
591
|
}
|
|
555
592
|
// TODO FW-3194 - Remove this
|
|
556
593
|
renderLegacySelect() {
|
|
@@ -215,6 +215,12 @@ button {
|
|
|
215
215
|
margin-top: 0;
|
|
216
216
|
margin-bottom: 0;
|
|
217
217
|
position: relative;
|
|
218
|
+
/**
|
|
219
|
+
* Prevent the icon from shrinking when the label and/or
|
|
220
|
+
* selected item text is long enough to fill the rest of
|
|
221
|
+
* the container.
|
|
222
|
+
*/
|
|
223
|
+
flex-shrink: 0;
|
|
218
224
|
}
|
|
219
225
|
|
|
220
226
|
/**
|
|
@@ -298,6 +304,23 @@ button {
|
|
|
298
304
|
transition: opacity 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
299
305
|
}
|
|
300
306
|
|
|
307
|
+
.select-wrapper-inner {
|
|
308
|
+
display: flex;
|
|
309
|
+
align-items: center;
|
|
310
|
+
overflow: hidden;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
:host(.select-label-placement-stacked) .select-wrapper-inner,
|
|
314
|
+
:host(.select-label-placement-floating) .select-wrapper-inner {
|
|
315
|
+
/**
|
|
316
|
+
* When using a stacked/floating label, the inner wrapper is
|
|
317
|
+
* stacked vertically under the label container. This line
|
|
318
|
+
* ensures that the inner wrapper fills all the remaining height
|
|
319
|
+
* of the component.
|
|
320
|
+
*/
|
|
321
|
+
flex-grow: 1;
|
|
322
|
+
}
|
|
323
|
+
|
|
301
324
|
:host(.ion-touched.ion-invalid) {
|
|
302
325
|
--highlight-color: var(--highlight-color-invalid);
|
|
303
326
|
}
|
|
@@ -538,11 +561,23 @@ button {
|
|
|
538
561
|
* The placeholder should be hidden when the label
|
|
539
562
|
* is on top of the select. This prevents the label
|
|
540
563
|
* from overlapping any placeholder value.
|
|
564
|
+
*
|
|
565
|
+
* TODO(FW-5592): Remove :not(.label-floating) piece
|
|
541
566
|
*/
|
|
542
|
-
:host(.select-label-placement-floating) .native-wrapper .select-placeholder {
|
|
567
|
+
:host(.select-label-placement-floating:not(.label-floating)) .native-wrapper .select-placeholder {
|
|
543
568
|
opacity: 0;
|
|
544
569
|
}
|
|
545
570
|
|
|
571
|
+
/**
|
|
572
|
+
* We don't use .label-floating here because that would
|
|
573
|
+
* also include the case where the label is floating due
|
|
574
|
+
* to content in the start/end slot. We want the opacity
|
|
575
|
+
* to remain at the default in this case, since the select
|
|
576
|
+
* isn't being actively interacted with.
|
|
577
|
+
*
|
|
578
|
+
* TODO(FW-5592): Change entire selector to:
|
|
579
|
+
* :host(.label-floating.select-label-placement-floating) .native-wrapper .select-placeholder
|
|
580
|
+
*/
|
|
546
581
|
:host(.select-expanded.select-label-placement-floating) .native-wrapper .select-placeholder,
|
|
547
582
|
:host(.ion-focused.select-label-placement-floating) .native-wrapper .select-placeholder,
|
|
548
583
|
:host(.has-value.select-label-placement-floating) .native-wrapper .select-placeholder {
|
|
@@ -552,10 +587,7 @@ button {
|
|
|
552
587
|
/**
|
|
553
588
|
* This makes the label sit above the input.
|
|
554
589
|
*/
|
|
555
|
-
:host(.
|
|
556
|
-
:host(.select-expanded.select-label-placement-floating) .label-text-wrapper,
|
|
557
|
-
:host(.ion-focused.select-label-placement-floating) .label-text-wrapper,
|
|
558
|
-
:host(.has-value.select-label-placement-floating) .label-text-wrapper {
|
|
590
|
+
:host(.label-floating) .label-text-wrapper {
|
|
559
591
|
transform: translateY(50%) scale(0.75);
|
|
560
592
|
/**
|
|
561
593
|
* Label text should not extend
|
|
@@ -564,6 +596,23 @@ button {
|
|
|
564
596
|
max-width: calc(100% / 0.75);
|
|
565
597
|
}
|
|
566
598
|
|
|
599
|
+
::slotted([slot=start]), ::slotted([slot=end]) {
|
|
600
|
+
/**
|
|
601
|
+
* Prevent the slots from shrinking when the label and/or
|
|
602
|
+
* selected item text is long enough to fill the rest of
|
|
603
|
+
* the container.
|
|
604
|
+
*/
|
|
605
|
+
flex-shrink: 0;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
::slotted([slot=start]) {
|
|
609
|
+
margin-inline-end: 16px;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
::slotted([slot=end]) {
|
|
613
|
+
margin-inline-start: 16px;
|
|
614
|
+
}
|
|
615
|
+
|
|
567
616
|
/**
|
|
568
617
|
* Convert a font size to a dynamic font size.
|
|
569
618
|
* Fonts that participate in Dynamic Type should use
|
|
@@ -741,10 +790,7 @@ button {
|
|
|
741
790
|
}
|
|
742
791
|
}
|
|
743
792
|
|
|
744
|
-
:host(.select-fill-solid
|
|
745
|
-
:host(.select-expanded.select-fill-solid.select-label-placement-floating) .label-text-wrapper,
|
|
746
|
-
:host(.ion-focused.select-fill-solid.select-label-placement-floating) .label-text-wrapper,
|
|
747
|
-
:host(.has-value.select-fill-solid.select-label-placement-floating) .label-text-wrapper {
|
|
793
|
+
:host(.label-floating.select-fill-solid) .label-text-wrapper {
|
|
748
794
|
/**
|
|
749
795
|
* Label text should not extend
|
|
750
796
|
* beyond the bounds of the select.
|
|
@@ -878,10 +924,7 @@ button {
|
|
|
878
924
|
/**
|
|
879
925
|
* This makes the label sit above the select.
|
|
880
926
|
*/
|
|
881
|
-
:host(.
|
|
882
|
-
:host(.ion-focused.select-fill-outline.select-label-placement-floating) .label-text-wrapper,
|
|
883
|
-
:host(.has-value.select-fill-outline.select-label-placement-floating) .label-text-wrapper,
|
|
884
|
-
:host(.select-fill-outline.select-label-placement-stacked) .label-text-wrapper {
|
|
927
|
+
:host(.label-floating.select-fill-outline) .label-text-wrapper {
|
|
885
928
|
transform: translateY(-32%) scale(0.75);
|
|
886
929
|
margin-left: 0;
|
|
887
930
|
margin-right: 0;
|
|
@@ -1026,10 +1069,7 @@ button {
|
|
|
1026
1069
|
* the floating/stacked label. We simulate this "cut out"
|
|
1027
1070
|
* by removing the top border from the notch fragment.
|
|
1028
1071
|
*/
|
|
1029
|
-
:host(.
|
|
1030
|
-
:host(.ion-focused.select-fill-outline.select-label-placement-floating) .select-outline-notch,
|
|
1031
|
-
:host(.has-value.select-fill-outline.select-label-placement-floating) .select-outline-notch,
|
|
1032
|
-
:host(.select-fill-outline.select-label-placement-stacked) .select-outline-notch {
|
|
1072
|
+
:host(.label-floating.select-fill-outline) .select-outline-notch {
|
|
1033
1073
|
border-top: none;
|
|
1034
1074
|
}
|
|
1035
1075
|
|
|
@@ -1194,8 +1234,8 @@ button {
|
|
|
1194
1234
|
--border-radius: 16px;
|
|
1195
1235
|
}
|
|
1196
1236
|
|
|
1197
|
-
:host(.select-label-placement-stacked) .
|
|
1198
|
-
:host(.select-label-placement-floating) .
|
|
1237
|
+
:host(.select-label-placement-stacked) .select-wrapper-inner,
|
|
1238
|
+
:host(.select-label-placement-floating) .select-wrapper-inner {
|
|
1199
1239
|
width: calc(100% - 0.8125rem - 4px);
|
|
1200
1240
|
}
|
|
1201
1241
|
|
|
@@ -121,7 +121,7 @@ configs().forEach(({ title, screenshot, config }) => {
|
|
|
121
121
|
});
|
|
122
122
|
test('label should appear on top of the select when the select is expanded', async ({ page }) => {
|
|
123
123
|
await page.setContent(`
|
|
124
|
-
<ion-select class="select-expanded" label="Label" label-placement="floating" placeholder="Select a Fruit">
|
|
124
|
+
<ion-select class="select-expanded label-floating" label="Label" label-placement="floating" placeholder="Select a Fruit">
|
|
125
125
|
<ion-select-option value="apples">Apples</ion-select-option>
|
|
126
126
|
</ion-select>
|
|
127
127
|
`, config);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
|
+
*/
|
|
4
|
+
import { expect } from "@playwright/test";
|
|
5
|
+
import { configs, test } from "../../../../utils/test/playwright/index";
|
|
6
|
+
configs().forEach(({ title, screenshot, config }) => {
|
|
7
|
+
test.describe(title('select: start and end slots (visual checks)'), () => {
|
|
8
|
+
test('should not have visual regressions with a start-positioned label', async ({ page }) => {
|
|
9
|
+
await page.setContent(`
|
|
10
|
+
<ion-select label-placement="start" fill="solid" placeholder="Select weight" label="Weight">
|
|
11
|
+
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
|
|
12
|
+
<ion-label slot="end">lbs</ion-label>
|
|
13
|
+
</ion-select>
|
|
14
|
+
`, config);
|
|
15
|
+
const select = page.locator('ion-select');
|
|
16
|
+
await expect(select).toHaveScreenshot(screenshot(`select-slots-label-start`));
|
|
17
|
+
});
|
|
18
|
+
test('should not have visual regressions with a floating label', async ({ page }) => {
|
|
19
|
+
await page.setContent(`
|
|
20
|
+
<ion-select label-placement="floating" fill="solid" placeholder="Select weight" label="Weight">
|
|
21
|
+
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
|
|
22
|
+
<ion-label slot="end">lbs</ion-label>
|
|
23
|
+
</ion-select>
|
|
24
|
+
`, config);
|
|
25
|
+
const select = page.locator('ion-select');
|
|
26
|
+
await expect(select).toHaveScreenshot(screenshot(`select-slots-label-floating`));
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
|
31
|
+
test.describe(title('select: start and end slots (functionality checks)'), () => {
|
|
32
|
+
test('should raise floating label when there is content in the start slot', async ({ page }) => {
|
|
33
|
+
await page.setContent(`
|
|
34
|
+
<ion-select label-placement="floating" fill="solid" label="Weight">
|
|
35
|
+
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
|
|
36
|
+
</ion-select>
|
|
37
|
+
`, config);
|
|
38
|
+
const select = page.locator('ion-select');
|
|
39
|
+
await expect(select).toHaveClass(/label-floating/);
|
|
40
|
+
});
|
|
41
|
+
test('should raise floating label when there is content in the end slot', async ({ page }) => {
|
|
42
|
+
await page.setContent(`
|
|
43
|
+
<ion-select label-placement="floating" fill="solid" label="Weight">
|
|
44
|
+
<ion-icon slot="end" name="barbell" aria-hidden="true"></ion-icon>
|
|
45
|
+
</ion-select>
|
|
46
|
+
`, config);
|
|
47
|
+
const select = page.locator('ion-select');
|
|
48
|
+
await expect(select).toHaveClass(/label-floating/);
|
|
49
|
+
});
|
|
50
|
+
test('should not open select when slotted buttons are clicked', async ({ page }) => {
|
|
51
|
+
await page.setContent(`
|
|
52
|
+
<ion-select label="Favorite Pizza" placeholder="Select a pizza">
|
|
53
|
+
<ion-select-option value="pepperoni">Pepperoni</ion-select-option>
|
|
54
|
+
<ion-select-option value="supreme">Supreme</ion-select-option>
|
|
55
|
+
<ion-select-option value="chicken">Chicken</ion-select-option>
|
|
56
|
+
<ion-button fill="clear" slot="end" aria-label="Show/hide password">
|
|
57
|
+
<ion-icon slot="icon-only" name="eye" aria-hidden="true"></ion-icon>
|
|
58
|
+
</ion-button>
|
|
59
|
+
</ion-select>
|
|
60
|
+
`, config);
|
|
61
|
+
await page.click('ion-select ion-button[slot="end"]');
|
|
62
|
+
await page.waitForChanges();
|
|
63
|
+
const select = page.locator('ion-select');
|
|
64
|
+
await expect(select).not.toHaveClass(/select-expanded/);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* (C) Ionic http://ionicframework.com - MIT License
|
|
3
|
+
*/
|
|
4
|
+
import { expect } from "@playwright/test";
|
|
5
|
+
import { configs, test } from "../../../../utils/test/playwright/index";
|
|
6
|
+
configs().forEach(({ title, screenshot, config }) => {
|
|
7
|
+
test.describe(title('textarea: start and end slots (visual checks)'), () => {
|
|
8
|
+
test('should not have visual regressions with a start-positioned label', async ({ page }) => {
|
|
9
|
+
await page.setContent(`
|
|
10
|
+
<ion-textarea label-placement="start" fill="solid" value="100" label="Weight">
|
|
11
|
+
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
|
|
12
|
+
<ion-label slot="end">lbs</ion-label>
|
|
13
|
+
</ion-textarea>
|
|
14
|
+
`, config);
|
|
15
|
+
const textarea = page.locator('ion-textarea');
|
|
16
|
+
await expect(textarea).toHaveScreenshot(screenshot(`textarea-slots-label-start`));
|
|
17
|
+
});
|
|
18
|
+
test('should not have visual regressions with a floating label', async ({ page }) => {
|
|
19
|
+
await page.setContent(`
|
|
20
|
+
<ion-textarea label-placement="floating" fill="solid" value="100" label="Weight">
|
|
21
|
+
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
|
|
22
|
+
<ion-label slot="end">lbs</ion-label>
|
|
23
|
+
</ion-textarea>
|
|
24
|
+
`, config);
|
|
25
|
+
const textarea = page.locator('ion-textarea');
|
|
26
|
+
await expect(textarea).toHaveScreenshot(screenshot(`textarea-slots-label-floating`));
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
|
31
|
+
test.describe(title('textarea: start and end slots (functionality checks)'), () => {
|
|
32
|
+
test('should raise floating label when there is content in the start slot', async ({ page }) => {
|
|
33
|
+
await page.setContent(`
|
|
34
|
+
<ion-textarea label-placement="floating" fill="solid" label="Weight">
|
|
35
|
+
<ion-icon slot="start" name="barbell" aria-hidden="true"></ion-icon>
|
|
36
|
+
</ion-textarea>
|
|
37
|
+
`, config);
|
|
38
|
+
const textarea = page.locator('ion-textarea');
|
|
39
|
+
await expect(textarea).toHaveClass(/label-floating/);
|
|
40
|
+
});
|
|
41
|
+
test('should raise floating label when there is content in the end slot', async ({ page }) => {
|
|
42
|
+
await page.setContent(`
|
|
43
|
+
<ion-textarea label-placement="floating" fill="solid" label="Weight">
|
|
44
|
+
<ion-icon slot="end" name="barbell" aria-hidden="true"></ion-icon>
|
|
45
|
+
</ion-textarea>
|
|
46
|
+
`, config);
|
|
47
|
+
const textarea = page.locator('ion-textarea');
|
|
48
|
+
await expect(textarea).toHaveClass(/label-floating/);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -334,9 +334,7 @@
|
|
|
334
334
|
}
|
|
335
335
|
|
|
336
336
|
.native-wrapper {
|
|
337
|
-
display: flex;
|
|
338
337
|
position: relative;
|
|
339
|
-
flex-grow: 1;
|
|
340
338
|
width: 100%;
|
|
341
339
|
height: 100%;
|
|
342
340
|
}
|
|
@@ -407,6 +405,12 @@
|
|
|
407
405
|
word-break: break-word;
|
|
408
406
|
}
|
|
409
407
|
|
|
408
|
+
.textarea-wrapper-inner {
|
|
409
|
+
display: flex;
|
|
410
|
+
width: 100%;
|
|
411
|
+
min-height: inherit;
|
|
412
|
+
}
|
|
413
|
+
|
|
410
414
|
:host(.ion-touched.ion-invalid) {
|
|
411
415
|
--highlight-color: var(--highlight-color-invalid);
|
|
412
416
|
}
|
|
@@ -682,6 +686,13 @@
|
|
|
682
686
|
margin-bottom: 0px;
|
|
683
687
|
}
|
|
684
688
|
|
|
689
|
+
:host(.textarea-label-placement-stacked) ::slotted([slot=start]),
|
|
690
|
+
:host(.textarea-label-placement-stacked) ::slotted([slot=end]),
|
|
691
|
+
:host(.textarea-label-placement-floating) ::slotted([slot=start]),
|
|
692
|
+
:host(.textarea-label-placement-floating) ::slotted([slot=end]) {
|
|
693
|
+
margin-top: 8px;
|
|
694
|
+
}
|
|
695
|
+
|
|
685
696
|
/**
|
|
686
697
|
* This makes the label sit over the textarea
|
|
687
698
|
* when the textarea is blurred and has no value.
|
|
@@ -707,9 +718,7 @@
|
|
|
707
718
|
/**
|
|
708
719
|
* This makes the label sit above the textarea.
|
|
709
720
|
*/
|
|
710
|
-
:host(.
|
|
711
|
-
:host(.has-focus.textarea-label-placement-floating) .label-text-wrapper,
|
|
712
|
-
:host(.has-value.textarea-label-placement-floating) .label-text-wrapper {
|
|
721
|
+
:host(.label-floating) .label-text-wrapper {
|
|
713
722
|
transform: translateY(50%) scale(0.75);
|
|
714
723
|
/**
|
|
715
724
|
* Label text should not extend
|
|
@@ -718,6 +727,29 @@
|
|
|
718
727
|
max-width: calc(100% / 0.75);
|
|
719
728
|
}
|
|
720
729
|
|
|
730
|
+
.start-slot-wrapper, .end-slot-wrapper {
|
|
731
|
+
padding-left: 0;
|
|
732
|
+
padding-right: 0;
|
|
733
|
+
padding-top: var(--padding-top);
|
|
734
|
+
padding-bottom: var(--padding-bottom);
|
|
735
|
+
display: flex;
|
|
736
|
+
flex-shrink: 0;
|
|
737
|
+
align-self: start;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
::slotted([slot=start]),
|
|
741
|
+
::slotted([slot=end]) {
|
|
742
|
+
margin-top: 0;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
::slotted([slot=start]) {
|
|
746
|
+
margin-inline-end: 16px;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
::slotted([slot=end]) {
|
|
750
|
+
margin-inline-start: 16px;
|
|
751
|
+
}
|
|
752
|
+
|
|
721
753
|
/**
|
|
722
754
|
* Convert a font size to a dynamic font size.
|
|
723
755
|
* Fonts that participate in Dynamic Type should use
|