use reactive controller for slot detection

pull/642/head
Cory LaViska 2022-01-05 18:31:41 -05:00
rodzic 1e3bac6031
commit 46f05224ab
11 zmienionych plików z 128 dodań i 180 usunięć

Wyświetl plik

@ -19,6 +19,7 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
- Improved `<sl-spinner>` track color when used on various backgrounds
- Improved a11y in `<sl-radio>` so VoiceOver announces radios properly in a radio group
- Improved the API for the experimental `<sl-split-panel>` component by making `position` accept a percentage and adding the `position-in-pixels` attribute
- Refactored `<sl-breadcrumb-item>`, `<sl-button>`, `<sl-card>`, `<sl-dialog>`, `<sl-drawer>`, `<sl-input>`, `<sl-range>`, `<sl-select>`, and `<sl-textarea>` to use a Reactive Controller for slot detection
- Refactored internal id usage in `<sl-details>`, `<sl-dialog>`, `<sl-drawer>`, and `<sl-dropdown>`
- Removed `position: relative` from the common component stylesheet

Wyświetl plik

@ -1,8 +1,8 @@
import { LitElement, html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { customElement, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { hasSlot } from '../../internal/slot';
import { HasSlotController } from '../../internal/slot';
import styles from './breadcrumb-item.styles';
/**
@ -25,8 +25,7 @@ import styles from './breadcrumb-item.styles';
export default class SlBreadcrumbItem extends LitElement {
static styles = styles;
@state() hasPrefix = false;
@state() hasSuffix = false;
private hasSlotController = new HasSlotController(this, ['prefix', 'suffix']);
/**
* Optional URL to direct the user to when the breadcrumb item is activated. When set, a link will be rendered
@ -40,11 +39,6 @@ export default class SlBreadcrumbItem extends LitElement {
/** The `rel` attribute to use on the link. Only used when `href` is set. */
@property() rel: string = 'noreferrer noopener';
handleSlotChange() {
this.hasPrefix = hasSlot(this, 'prefix');
this.hasSuffix = hasSlot(this, 'suffix');
}
render() {
const isLink = this.href ? true : false;
@ -53,12 +47,12 @@ export default class SlBreadcrumbItem extends LitElement {
part="base"
class=${classMap({
'breadcrumb-item': true,
'breadcrumb-item--has-prefix': this.hasPrefix,
'breadcrumb-item--has-suffix': this.hasSuffix
'breadcrumb-item--has-prefix': this.hasSlotController.test('prefix'),
'breadcrumb-item--has-suffix': this.hasSlotController.test('suffix')
})}
>
<span part="prefix" class="breadcrumb-item__prefix">
<slot name="prefix" @slotchange=${this.handleSlotChange}></slot>
<slot name="prefix"></slot>
</span>
${isLink
@ -80,7 +74,7 @@ export default class SlBreadcrumbItem extends LitElement {
`}
<span part="suffix" class="breadcrumb-item__suffix">
<slot name="suffix" @slotchange=${this.handleSlotChange}></slot>
<slot name="suffix"></slot>
</span>
<span part="separator" class="breadcrumb-item__separator" aria-hidden="true">

Wyświetl plik

@ -4,7 +4,7 @@ import { html, literal } from 'lit/static-html.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { emit } from '../../internal/event';
import { hasSlot } from '../../internal/slot';
import { HasSlotController } from '../../internal/slot';
import styles from './button.styles';
import '../spinner/spinner';
@ -34,10 +34,9 @@ export default class SlButton extends LitElement {
@query('.button') button: HTMLButtonElement | HTMLLinkElement;
private hasSlotController = new HasSlotController(this, ['[default]', 'prefix', 'suffix']);
@state() private hasFocus = false;
@state() private hasLabel = false;
@state() private hasPrefix = false;
@state() private hasSuffix = false;
/** The button's variant. */
@property({ reflect: true }) variant: 'default' | 'primary' | 'success' | 'neutral' | 'warning' | 'danger' | 'text' =
@ -82,11 +81,6 @@ export default class SlButton extends LitElement {
/** Tells the browser to download the linked file as this filename. Only used when `href` is set. */
@property() download: string;
connectedCallback() {
super.connectedCallback();
this.handleSlotChange();
}
/** Simulates a click on the button. */
click() {
this.button.click();
@ -102,12 +96,6 @@ export default class SlButton extends LitElement {
this.button.blur();
}
handleSlotChange() {
this.hasLabel = hasSlot(this);
this.hasPrefix = hasSlot(this, 'prefix');
this.hasSuffix = hasSlot(this, 'suffix');
}
handleBlur() {
this.hasFocus = false;
emit(this, 'sl-blur');
@ -152,9 +140,9 @@ export default class SlButton extends LitElement {
'button--standard': !this.outline,
'button--outline': this.outline,
'button--pill': this.pill,
'button--has-label': this.hasLabel,
'button--has-prefix': this.hasPrefix,
'button--has-suffix': this.hasSuffix
'button--has-label': this.hasSlotController.test('[default]'),
'button--has-prefix': this.hasSlotController.test('prefix'),
'button--has-suffix': this.hasSlotController.test('suffix')
})}
?disabled=${ifDefined(isLink ? undefined : this.disabled)}
type=${ifDefined(isLink ? undefined : this.submit ? 'submit' : 'button')}
@ -172,13 +160,13 @@ export default class SlButton extends LitElement {
@click=${this.handleClick}
>
<span part="prefix" class="button__prefix">
<slot @slotchange=${this.handleSlotChange} name="prefix"></slot>
<slot name="prefix"></slot>
</span>
<span part="label" class="button__label">
<slot @slotchange=${this.handleSlotChange}></slot>
<slot></slot>
</span>
<span part="suffix" class="button__suffix">
<slot @slotchange=${this.handleSlotChange} name="suffix"></slot>
<slot name="suffix"></slot>
</span>
${
this.caret

Wyświetl plik

@ -1,7 +1,7 @@
import { LitElement, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { customElement } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { hasSlot } from '../../internal/slot';
import { HasSlotController } from '../../internal/slot';
import styles from './card.styles';
/**
@ -28,15 +28,7 @@ import styles from './card.styles';
export default class SlCard extends LitElement {
static styles = styles;
@state() private hasFooter = false;
@state() private hasImage = false;
@state() private hasHeader = false;
handleSlotChange() {
this.hasFooter = hasSlot(this, 'footer');
this.hasImage = hasSlot(this, 'image');
this.hasHeader = hasSlot(this, 'header');
}
private hasSlotController = new HasSlotController(this, ['footer', 'header', 'image']);
render() {
return html`
@ -44,17 +36,17 @@ export default class SlCard extends LitElement {
part="base"
class=${classMap({
card: true,
'card--has-footer': this.hasFooter,
'card--has-image': this.hasImage,
'card--has-header': this.hasHeader
'card--has-footer': this.hasSlotController.test('footer'),
'card--has-image': this.hasSlotController.test('image'),
'card--has-header': this.hasSlotController.test('header')
})}
>
<div part="image" class="card__image">
<slot name="image" @slotchange=${this.handleSlotChange}></slot>
<slot name="image"></slot>
</div>
<div part="header" class="card__header">
<slot name="header" @slotchange=${this.handleSlotChange}></slot>
<slot name="header"></slot>
</div>
<div part="body" class="card__body">
@ -62,7 +54,7 @@ export default class SlCard extends LitElement {
</div>
<div part="footer" class="card__footer">
<slot name="footer" @slotchange=${this.handleSlotChange}></slot>
<slot name="footer"></slot>
</div>
</div>
`;

Wyświetl plik

@ -1,5 +1,5 @@
import { LitElement, html } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import { customElement, property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { animateTo, stopAnimations } from '../../internal/animate';
@ -7,7 +7,7 @@ import { emit } from '../../internal/event';
import { watch } from '../../internal/watch';
import { waitForEvent } from '../../internal/event';
import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll';
import { hasSlot } from '../../internal/slot';
import { HasSlotController } from '../../internal/slot';
import { isPreventScrollSupported } from '../../internal/support';
import Modal from '../../internal/modal';
import { setDefaultAnimation, getAnimation } from '../../utilities/animation-registry';
@ -65,11 +65,10 @@ export default class SlDialog extends LitElement {
@query('.dialog__panel') panel: HTMLElement;
@query('.dialog__overlay') overlay: HTMLElement;
private hasSlotController = new HasSlotController(this, ['footer']);
private modal: Modal;
private originalTrigger: HTMLElement | null;
@state() private hasFooter = false;
/** Indicates whether or not the dialog is open. You can use this in lieu of the show/hide methods. */
@property({ type: Boolean, reflect: true }) open = false;
@ -87,9 +86,7 @@ export default class SlDialog extends LitElement {
connectedCallback() {
super.connectedCallback();
this.modal = new Modal(this);
this.handleSlotChange();
}
firstUpdated() {
@ -208,10 +205,6 @@ export default class SlDialog extends LitElement {
}
}
handleSlotChange() {
this.hasFooter = hasSlot(this, 'footer');
}
render() {
return html`
<div
@ -219,7 +212,7 @@ export default class SlDialog extends LitElement {
class=${classMap({
dialog: true,
'dialog--open': this.open,
'dialog--has-footer': this.hasFooter
'dialog--has-footer': this.hasSlotController.test('footer')
})}
@keydown=${this.handleKeyDown}
>
@ -257,7 +250,7 @@ export default class SlDialog extends LitElement {
</div>
<footer part="footer" class="dialog__footer">
<slot name="footer" @slotchange=${this.handleSlotChange}></slot>
<slot name="footer"></slot>
</footer>
</div>
</div>

Wyświetl plik

@ -1,5 +1,5 @@
import { LitElement, html } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import { customElement, property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { animateTo, stopAnimations } from '../../internal/animate';
@ -7,7 +7,7 @@ import { emit } from '../../internal/event';
import { watch } from '../../internal/watch';
import { waitForEvent } from '../../internal/event';
import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll';
import { hasSlot } from '../../internal/slot';
import { HasSlotController } from '../../internal/slot';
import { uppercaseFirstLetter } from '../../internal/string';
import { isPreventScrollSupported } from '../../internal/support';
import Modal from '../../internal/modal';
@ -73,11 +73,10 @@ export default class SlDrawer extends LitElement {
@query('.drawer__panel') panel: HTMLElement;
@query('.drawer__overlay') overlay: HTMLElement;
private hasSlotController = new HasSlotController(this, ['footer']);
private modal: Modal;
private originalTrigger: HTMLElement | null;
@state() private hasFooter = false;
/** Indicates whether or not the drawer is open. You can use this in lieu of the show/hide methods. */
@property({ type: Boolean, reflect: true }) open = false;
@ -104,9 +103,7 @@ export default class SlDrawer extends LitElement {
connectedCallback() {
super.connectedCallback();
this.modal = new Modal(this);
this.handleSlotChange();
}
firstUpdated() {
@ -228,10 +225,6 @@ export default class SlDrawer extends LitElement {
}
}
handleSlotChange() {
this.hasFooter = hasSlot(this, 'footer');
}
render() {
return html`
<div
@ -245,7 +238,7 @@ export default class SlDrawer extends LitElement {
'drawer--start': this.placement === 'start',
'drawer--contained': this.contained,
'drawer--fixed': !this.contained,
'drawer--has-footer': this.hasFooter
'drawer--has-footer': this.hasSlotController.test('footer')
})}
@keydown=${this.handleKeyDown}
>
@ -284,7 +277,7 @@ export default class SlDrawer extends LitElement {
</div>
<footer part="footer" class="drawer__footer">
<slot name="footer" @slotchange=${this.handleSlotChange}></slot>
<slot name="footer"></slot>
</footer>
</div>
</div>

Wyświetl plik

@ -6,7 +6,7 @@ import { live } from 'lit/directives/live.js';
import { emit } from '../../internal/event';
import { watch } from '../../internal/watch';
import { getLabelledBy, renderFormControl } from '../../internal/form-control';
import { hasSlot } from '../../internal/slot';
import { HasSlotController } from '../../internal/slot';
import styles from './input.styles';
import '../icon/icon';
@ -49,13 +49,12 @@ export default class SlInput extends LitElement {
@query('.input__control') input: HTMLInputElement;
private hasSlotController = new HasSlotController(this, ['help-text', 'label']);
private inputId = `input-${++id}`;
private helpTextId = `input-help-text-${id}`;
private labelId = `input-label-${id}`;
@state() private hasFocus = false;
@state() private hasHelpTextSlot = false;
@state() private hasLabelSlot = false;
@state() private isPasswordVisible = false;
/** The input's type. */
@ -163,21 +162,10 @@ export default class SlInput extends LitElement {
this.value = this.input.value;
}
connectedCallback() {
super.connectedCallback();
this.handleSlotChange = this.handleSlotChange.bind(this);
this.shadowRoot!.addEventListener('slotchange', this.handleSlotChange);
}
firstUpdated() {
this.invalid = !this.input.checkValidity();
}
disconnectedCallback() {
super.disconnectedCallback();
this.shadowRoot!.removeEventListener('slotchange', this.handleSlotChange);
}
/** Sets focus on the input. */
focus(options?: FocusOptions) {
this.input.focus(options);
@ -276,13 +264,6 @@ export default class SlInput extends LitElement {
this.isPasswordVisible = !this.isPasswordVisible;
}
@watch('helpText')
@watch('label')
handleSlotChange() {
this.hasHelpTextSlot = hasSlot(this, 'help-text');
this.hasLabelSlot = hasSlot(this, 'label');
}
@watch('value')
handleValueChange() {
if (this.input) {
@ -291,16 +272,19 @@ export default class SlInput extends LitElement {
}
render() {
const hasLabelSlot = this.hasSlotController.test('label');
const hasHelpTextSlot = this.hasSlotController.test('help-text');
// NOTE - always bind value after min/max, otherwise it will be clamped
return renderFormControl(
{
inputId: this.inputId,
label: this.label,
labelId: this.labelId,
hasLabelSlot: this.hasLabelSlot,
hasLabelSlot,
helpTextId: this.helpTextId,
helpText: this.helpText,
hasHelpTextSlot: this.hasHelpTextSlot,
hasHelpTextSlot,
size: this.size
},
html`
@ -355,10 +339,10 @@ export default class SlInput extends LitElement {
getLabelledBy({
label: this.label,
labelId: this.labelId,
hasLabelSlot: this.hasLabelSlot,
hasLabelSlot,
helpText: this.helpText,
helpTextId: this.helpTextId,
hasHelpTextSlot: this.hasHelpTextSlot
hasHelpTextSlot
})
)}
aria-invalid=${this.invalid ? 'true' : 'false'}

Wyświetl plik

@ -6,7 +6,7 @@ import { emit } from '../../internal/event';
import { live } from 'lit/directives/live.js';
import { watch } from '../../internal/watch';
import { getLabelledBy, renderFormControl } from '../../internal/form-control';
import { hasSlot } from '../../internal/slot';
import { HasSlotController } from '../../internal/slot';
import styles from './range.styles';
let id = 0;
@ -39,14 +39,13 @@ export default class SlRange extends LitElement {
@query('.range__control') input: HTMLInputElement;
@query('.range__tooltip') output: HTMLOutputElement;
private hasSlotController = new HasSlotController(this, ['help-text', 'label']);
private inputId = `input-${++id}`;
private helpTextId = `input-help-text-${id}`;
private labelId = `input-label-${id}`;
private resizeObserver: ResizeObserver;
@state() private hasFocus = false;
@state() private hasHelpTextSlot = false;
@state() private hasLabelSlot = false;
@state() private hasTooltip = false;
/** The input's name attribute. */
@ -88,14 +87,11 @@ export default class SlRange extends LitElement {
connectedCallback() {
super.connectedCallback();
this.resizeObserver = new ResizeObserver(() => this.syncRange());
this.shadowRoot!.addEventListener('slotchange', this.handleSlotChange);
if (this.value === undefined || this.value === null) this.value = this.min;
if (this.value < this.min) this.value = this.min;
if (this.value > this.max) this.value = this.max;
this.handleSlotChange();
this.updateComplete.then(() => {
this.syncRange();
this.resizeObserver.observe(this.input);
@ -105,7 +101,6 @@ export default class SlRange extends LitElement {
disconnectedCallback() {
super.disconnectedCallback();
this.resizeObserver.unobserve(this.input);
this.shadowRoot!.removeEventListener('slotchange', this.handleSlotChange);
}
/** Sets focus on the input. */
@ -163,13 +158,6 @@ export default class SlRange extends LitElement {
emit(this, 'sl-focus');
}
@watch('label')
@watch('helpText')
handleSlotChange() {
this.hasHelpTextSlot = hasSlot(this, 'help-text');
this.hasLabelSlot = hasSlot(this, 'label');
}
handleThumbDragStart() {
this.hasTooltip = true;
}
@ -207,16 +195,19 @@ export default class SlRange extends LitElement {
}
render() {
const hasLabelSlot = this.hasSlotController.test('label');
const hasHelpTextSlot = this.hasSlotController.test('help-text');
// NOTE - always bind value after min/max, otherwise it will be clamped
return renderFormControl(
{
inputId: this.inputId,
label: this.label,
labelId: this.labelId,
hasLabelSlot: this.hasLabelSlot,
hasLabelSlot,
helpTextId: this.helpTextId,
helpText: this.helpText,
hasHelpTextSlot: this.hasHelpTextSlot,
hasHelpTextSlot,
size: 'medium'
},
html`
@ -249,10 +240,10 @@ export default class SlRange extends LitElement {
getLabelledBy({
label: this.label,
labelId: this.labelId,
hasLabelSlot: this.hasLabelSlot,
hasLabelSlot,
helpText: this.helpText,
helpTextId: this.helpTextId,
hasHelpTextSlot: this.hasHelpTextSlot
hasHelpTextSlot
})
)}
@input=${this.handleInput}

Wyświetl plik

@ -6,7 +6,7 @@ import { emit } from '../../internal/event';
import { watch } from '../../internal/watch';
import { getLabelledBy, renderFormControl } from '../../internal/form-control';
import { getTextContent } from '../../internal/slot';
import { hasSlot } from '../../internal/slot';
import { HasSlotController } from '../../internal/slot';
import type SlDropdown from '../dropdown/dropdown';
import type SlIconButton from '../icon-button/icon-button';
import type SlMenu from '../menu/menu';
@ -65,14 +65,13 @@ export default class SlSelect extends LitElement {
@query('.select__hidden-select') input: HTMLInputElement;
@query('.select__menu') menu: SlMenu;
private hasSlotController = new HasSlotController(this, ['help-text', 'label']);
private inputId = `select-${++id}`;
private helpTextId = `select-help-text-${id}`;
private labelId = `select-label-${id}`;
private resizeObserver: ResizeObserver;
@state() private hasFocus = false;
@state() private hasHelpTextSlot = false;
@state() private hasLabelSlot = false;
@state() private isOpen = false;
@state() private displayLabel = '';
@state() private displayTags: TemplateResult[] = [];
@ -130,12 +129,11 @@ export default class SlSelect extends LitElement {
connectedCallback() {
super.connectedCallback();
this.handleSlotChange = this.handleSlotChange.bind(this);
this.handleMenuSlotChange = this.handleMenuSlotChange.bind(this);
this.resizeObserver = new ResizeObserver(() => this.resizeMenu());
this.updateComplete.then(() => {
this.resizeObserver.observe(this);
this.shadowRoot!.addEventListener('slotchange', this.handleSlotChange);
this.syncItemsFromValue();
});
}
@ -147,7 +145,6 @@ export default class SlSelect extends LitElement {
disconnectedCallback() {
super.disconnectedCallback();
this.resizeObserver.unobserve(this);
this.shadowRoot!.removeEventListener('slotchange', this.handleSlotChange);
}
/** Checks for validity and shows the browser's validation message if the control is invalid. */
@ -318,12 +315,7 @@ export default class SlSelect extends LitElement {
this.syncItemsFromValue();
}
@watch('helpText')
@watch('label')
async handleSlotChange() {
this.hasHelpTextSlot = hasSlot(this, 'help-text');
this.hasLabelSlot = hasSlot(this, 'label');
async handleMenuSlotChange() {
// Wait for items to render before gathering labels otherwise the slot won't exist
const items = this.getItems();
@ -437,6 +429,8 @@ export default class SlSelect extends LitElement {
}
render() {
const hasLabelSlot = this.hasSlotController.test('label');
const hasHelpTextSlot = this.hasSlotController.test('help-text');
const hasSelection = this.multiple ? this.value?.length > 0 : this.value !== '';
return renderFormControl(
@ -444,10 +438,10 @@ export default class SlSelect extends LitElement {
inputId: this.inputId,
label: this.label,
labelId: this.labelId,
hasLabelSlot: this.hasLabelSlot,
hasLabelSlot,
helpTextId: this.helpTextId,
helpText: this.helpText,
hasHelpTextSlot: this.hasHelpTextSlot,
hasHelpTextSlot,
size: this.size,
onLabelClick: () => this.handleLabelClick()
},
@ -489,10 +483,10 @@ export default class SlSelect extends LitElement {
getLabelledBy({
label: this.label,
labelId: this.labelId,
hasLabelSlot: this.hasLabelSlot,
hasLabelSlot,
helpText: this.helpText,
helpTextId: this.helpTextId,
hasHelpTextSlot: this.hasHelpTextSlot
hasHelpTextSlot
})
)}
aria-haspopup="true"
@ -548,7 +542,7 @@ export default class SlSelect extends LitElement {
</div>
<sl-menu part="menu" class="select__menu" @sl-select=${this.handleMenuSelect}>
<slot @slotchange=${this.handleSlotChange}></slot>
<slot @slotchange=${this.handleMenuSlotChange}></slot>
</sl-menu>
</sl-dropdown>
`

Wyświetl plik

@ -6,7 +6,7 @@ import { emit } from '../../internal/event';
import { live } from 'lit/directives/live.js';
import { watch } from '../../internal/watch';
import { getLabelledBy, renderFormControl } from '../../internal/form-control';
import { hasSlot } from '../../internal/slot';
import { HasSlotController } from '../../internal/slot';
import styles from './textarea.styles';
let id = 0;
@ -35,14 +35,13 @@ export default class SlTextarea extends LitElement {
@query('.textarea__control') input: HTMLTextAreaElement;
private hasSlotController = new HasSlotController(this, ['help-text', 'label']);
private inputId = `textarea-${++id}`;
private helpTextId = `textarea-help-text-${id}`;
private labelId = `textarea-label-${id}`;
private resizeObserver: ResizeObserver;
@state() private hasFocus = false;
@state() private hasHelpTextSlot = false;
@state() private hasLabelSlot = false;
/** The textarea's size. */
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
@ -115,10 +114,7 @@ export default class SlTextarea extends LitElement {
connectedCallback() {
super.connectedCallback();
this.handleSlotChange = this.handleSlotChange.bind(this);
this.resizeObserver = new ResizeObserver(() => this.setTextareaHeight());
this.shadowRoot!.addEventListener('slotchange', this.handleSlotChange);
this.handleSlotChange();
this.updateComplete.then(() => {
this.setTextareaHeight();
@ -133,7 +129,6 @@ export default class SlTextarea extends LitElement {
disconnectedCallback() {
super.disconnectedCallback();
this.resizeObserver.unobserve(this.input);
this.shadowRoot!.removeEventListener('slotchange', this.handleSlotChange);
}
/** Sets focus on the textarea. */
@ -243,13 +238,6 @@ export default class SlTextarea extends LitElement {
this.setTextareaHeight();
}
@watch('helpText')
@watch('label')
handleSlotChange() {
this.hasHelpTextSlot = hasSlot(this, 'help-text');
this.hasLabelSlot = hasSlot(this, 'label');
}
@watch('value')
handleValueChange() {
if (this.input) {
@ -269,15 +257,18 @@ export default class SlTextarea extends LitElement {
}
render() {
const hasLabelSlot = this.hasSlotController.test('label');
const hasHelpTextSlot = this.hasSlotController.test('help-text');
return renderFormControl(
{
inputId: this.inputId,
label: this.label,
labelId: this.labelId,
hasLabelSlot: this.hasLabelSlot,
hasLabelSlot,
helpTextId: this.helpTextId,
helpText: this.helpText,
hasHelpTextSlot: this.hasHelpTextSlot,
hasHelpTextSlot,
size: this.size
},
html`
@ -321,10 +312,10 @@ export default class SlTextarea extends LitElement {
getLabelledBy({
label: this.label,
labelId: this.labelId,
hasLabelSlot: this.hasLabelSlot,
hasLabelSlot,
helpText: this.helpText,
helpTextId: this.helpTextId,
hasHelpTextSlot: this.hasHelpTextSlot
hasHelpTextSlot
})
)}
@change=${this.handleChange}

Wyświetl plik

@ -1,3 +1,57 @@
import { ReactiveController, ReactiveControllerHost } from 'lit';
export class HasSlotController implements ReactiveController {
host: ReactiveControllerHost & Element;
slotNames: string[] = [];
constructor(host: ReactiveControllerHost & Element, slotNames: string[] = []) {
(this.host = host).addController(this);
this.slotNames = slotNames;
this.handleSlotChange = this.handleSlotChange.bind(this);
}
private hasDefaultSlot() {
return [...this.host.childNodes].some(node => {
if (node.nodeType === node.TEXT_NODE && node.textContent!.trim() !== '') {
return true;
}
if (node.nodeType === node.ELEMENT_NODE) {
const el = node as HTMLElement;
if (!el.hasAttribute('slot')) {
return true;
}
}
return false;
});
}
private hasNamedSlot(name: string) {
return this.host.querySelector(`:scope > [slot="${name}"]`) !== null;
}
test(slotName: string) {
return slotName === '[default]' ? this.hasDefaultSlot() : this.hasNamedSlot(slotName);
}
hostConnected() {
this.host.shadowRoot!.addEventListener('slotchange', this.handleSlotChange);
}
hostDisconnected() {
this.host.shadowRoot!.removeEventListener('slotchange', this.handleSlotChange);
}
handleSlotChange(event: Event) {
const slot = event.target as HTMLSlotElement;
if ((this.slotNames.includes('[default]') && !slot.name) || (slot.name && this.slotNames?.includes(slot.name))) {
this.host.requestUpdate();
}
}
}
//
// Given a slot, this function iterates over all of its assigned element and text nodes and returns the concatenated
// HTML as a string. This is useful because we can't use slot.innerHTML as an alternative.
@ -35,30 +89,3 @@ export function getTextContent(slot: HTMLSlotElement): string {
return text;
}
//
// Determines whether an element has a slot. If name is specified, the function will look for a corresponding named
// slot, otherwise it will look for a "default" slot (e.g. a non-empty text node or an element with no slot attribute).
//
export function hasSlot(el: HTMLElement, name?: string) {
// Look for a named slot
if (name) {
return el.querySelector(`:scope > [slot="${name}"]`) !== null;
}
// Look for a default slot
return [...el.childNodes].some(node => {
if (node.nodeType === node.TEXT_NODE && node.textContent!.trim() !== '') {
return true;
}
if (node.nodeType === node.ELEMENT_NODE) {
const el = node as HTMLElement;
if (!el.hasAttribute('slot')) {
return true;
}
}
return false;
});
}