mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-10 00:27:41 +08:00
feat(input): add experimental label slot (#27650)
Issue number: resolves #27061 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Input does not accept custom HTML labels ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - Input accepts custom HTML labels as an experimental feature. We marked this as experimental because it makes use of "scoped slots" which is an emulated version of Web Component slots. As a result, there may be instances where the slot behavior does not exactly match the native slot behavior. Note to reviewers: This is a combination of previously reviewed PRs. The implementation is complete, so feel free to bikeshed. ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change, please describe the impact and migration path for existing applications below. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Docs PR: https://github.com/ionic-team/ionic-docs/pull/2997 --------- Co-authored-by: Brandy Carney <brandyscarney@users.noreply.github.com>
This commit is contained in:
@ -1,10 +1,12 @@
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Build, Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core';
|
||||
import type { LegacyFormController } from '@utils/forms';
|
||||
import { createLegacyFormController } from '@utils/forms';
|
||||
import { Build, Component, Element, Event, Host, Method, Prop, State, Watch, forceUpdate, h } from '@stencil/core';
|
||||
import type { LegacyFormController, NotchController } from '@utils/forms';
|
||||
import { createLegacyFormController, createNotchController } from '@utils/forms';
|
||||
import type { Attributes } from '@utils/helpers';
|
||||
import { inheritAriaAttributes, debounceEvent, findItemLabel, inheritAttributes } from '@utils/helpers';
|
||||
import { printIonWarning } from '@utils/logging';
|
||||
import { createSlotMutationController } from '@utils/slot-mutation-controller';
|
||||
import type { SlotMutationController } from '@utils/slot-mutation-controller';
|
||||
import { createColorClasses, hostContext } from '@utils/theme';
|
||||
import { closeCircle, closeSharp } from 'ionicons/icons';
|
||||
|
||||
@ -16,6 +18,8 @@ import { getCounterText } from './input.utils';
|
||||
|
||||
/**
|
||||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
||||
*
|
||||
* @slot label - The label text to associate with the input. Use the `labelPlacement` property to control where the label is placed relative to the input. Use this if you need to render a label with custom HTML. (EXPERIMENTAL)
|
||||
*/
|
||||
@Component({
|
||||
tag: 'ion-input',
|
||||
@ -31,6 +35,9 @@ export class Input implements ComponentInterface {
|
||||
private inheritedAttributes: Attributes = {};
|
||||
private isComposing = false;
|
||||
private legacyFormController!: LegacyFormController;
|
||||
private slotMutationController?: SlotMutationController;
|
||||
private notchController?: NotchController;
|
||||
private notchSpacerEl: HTMLElement | undefined;
|
||||
|
||||
// This flag ensures we log the deprecation warning at most once.
|
||||
private hasLoggedDeprecationWarning = false;
|
||||
@ -165,6 +172,10 @@ export class Input implements ComponentInterface {
|
||||
|
||||
/**
|
||||
* The visible label associated with the input.
|
||||
*
|
||||
* Use this if you need to render a plaintext label.
|
||||
*
|
||||
* The `label` property will take priority over the `label` slot if both are used.
|
||||
*/
|
||||
@Prop() label?: string;
|
||||
|
||||
@ -353,6 +364,12 @@ export class Input implements ComponentInterface {
|
||||
const { el } = this;
|
||||
|
||||
this.legacyFormController = createLegacyFormController(el);
|
||||
this.slotMutationController = createSlotMutationController(el, 'label', () => forceUpdate(this));
|
||||
this.notchController = createNotchController(
|
||||
el,
|
||||
() => this.notchSpacerEl,
|
||||
() => this.labelSlot
|
||||
);
|
||||
|
||||
this.emitStyle();
|
||||
this.debounceChanged();
|
||||
@ -369,6 +386,10 @@ export class Input implements ComponentInterface {
|
||||
this.originalIonInput = this.ionInput;
|
||||
}
|
||||
|
||||
componentDidRender() {
|
||||
this.notchController?.calculateNotchWidth();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (Build.isBrowser) {
|
||||
document.dispatchEvent(
|
||||
@ -377,6 +398,16 @@ export class Input implements ComponentInterface {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (this.slotMutationController) {
|
||||
this.slotMutationController.destroy();
|
||||
this.slotMutationController = undefined;
|
||||
}
|
||||
|
||||
if (this.notchController) {
|
||||
this.notchController.destroy();
|
||||
this.notchController = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -578,17 +609,37 @@ export class Input implements ComponentInterface {
|
||||
|
||||
private renderLabel() {
|
||||
const { label } = this;
|
||||
if (label === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="label-text-wrapper">
|
||||
<div class="label-text">{this.label}</div>
|
||||
<div
|
||||
class={{
|
||||
'label-text-wrapper': true,
|
||||
'label-text-wrapper-hidden': !this.hasLabel,
|
||||
}}
|
||||
>
|
||||
{label === undefined ? <slot name="label"></slot> : <div class="label-text">{label}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets any content passed into the `label` slot,
|
||||
* not the <slot> definition.
|
||||
*/
|
||||
private get labelSlot() {
|
||||
return this.el.querySelector('[slot="label"]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if label content is provided
|
||||
* either by a prop or a content. If you want
|
||||
* to get the plaintext value of the label use
|
||||
* the `labelText` getter instead.
|
||||
*/
|
||||
private get hasLabel() {
|
||||
return this.label !== undefined || this.labelSlot !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the border container
|
||||
* when fill="outline".
|
||||
@ -608,8 +659,13 @@ export class Input implements ComponentInterface {
|
||||
return [
|
||||
<div class="input-outline-container">
|
||||
<div class="input-outline-start"></div>
|
||||
<div class="input-outline-notch">
|
||||
<div class="notch-spacer" aria-hidden="true">
|
||||
<div
|
||||
class={{
|
||||
'input-outline-notch': true,
|
||||
'input-outline-notch-hidden': !this.hasLabel,
|
||||
}}
|
||||
>
|
||||
<div class="notch-spacer" aria-hidden="true" ref={(el) => (this.notchSpacerEl = el)}>
|
||||
{this.label}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user