@quick-game/cli
Version:
Command line interface for rapid qg development
236 lines • 9.53 kB
JavaScript
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as LitHtml from '../../lit-html/lit-html.js';
import * as ComponentHelpers from '../helpers/helpers.js';
import * as IconButton from '../icon_button/icon_button.js';
import buttonStyles from './button.css.js';
export class Button extends HTMLElement {
static formAssociated = true;
static litTagName = LitHtml.literal `devtools-button`;
#shadow = this.attachShadow({ mode: 'open', delegatesFocus: true });
#boundRender = this.#render.bind(this);
#boundOnClick = this.#onClick.bind(this);
#props = {
size: "MEDIUM" /* Size.MEDIUM */,
disabled: false,
active: false,
spinner: false,
type: 'button',
};
#isEmpty = true;
#internals = this.attachInternals();
constructor() {
super();
this.setAttribute('role', 'presentation');
this.addEventListener('click', this.#boundOnClick, true);
}
/**
* Perfer using the .data= setter instead of setting the individual properties
* for increased type-safety.
*/
set data(data) {
this.#props.variant = data.variant;
this.#props.iconUrl = data.iconUrl;
this.#props.iconName = data.iconName;
this.#props.size = "MEDIUM" /* Size.MEDIUM */;
if ('size' in data && data.size) {
this.#props.size = data.size;
}
if ('iconWidth' in data && data.iconWidth) {
this.#props.iconWidth = data.iconWidth;
}
if ('iconHeight' in data && data.iconHeight) {
this.#props.iconHeight = data.iconHeight;
}
this.#props.active = Boolean(data.active);
this.#props.spinner = Boolean('spinner' in data ? data.spinner : false);
this.#props.type = 'button';
if ('type' in data && data.type) {
this.#props.type = data.type;
}
this.#setDisabledProperty(data.disabled || false);
this.#props.title = data.title;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
set iconUrl(iconUrl) {
this.#props.iconUrl = iconUrl;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
set iconName(iconName) {
this.#props.iconName = iconName;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
set variant(variant) {
this.#props.variant = variant;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
set size(size) {
this.#props.size = size;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
set iconWidth(iconWidth) {
this.#props.iconWidth = iconWidth;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
set iconHeight(iconHeight) {
this.#props.iconHeight = iconHeight;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
set type(type) {
this.#props.type = type;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
set title(title) {
this.#props.title = title;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
set disabled(disabled) {
this.#setDisabledProperty(disabled);
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
set active(active) {
this.#props.active = active;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
set spinner(spinner) {
this.#props.spinner = spinner;
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
#setDisabledProperty(disabled) {
this.#props.disabled = disabled;
this.toggleAttribute('disabled', disabled);
}
focus() {
this.#shadow.querySelector('button')?.focus();
}
connectedCallback() {
this.#shadow.adoptedStyleSheets = [buttonStyles];
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
#onClick(event) {
if (this.#props.disabled) {
event.stopPropagation();
event.preventDefault();
return;
}
if (this.form && this.#props.type === 'submit') {
event.preventDefault();
this.form.dispatchEvent(new SubmitEvent('submit', {
submitter: this,
}));
}
if (this.form && this.#props.type === 'reset') {
event.preventDefault();
this.form.reset();
}
}
#onSlotChange(event) {
const slot = event.target;
const nodes = slot?.assignedNodes();
this.#isEmpty = !nodes || !Boolean(nodes.length);
void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
}
#isToolbarVariant() {
return this.#props.variant === "toolbar" /* Variant.TOOLBAR */ || this.#props.variant === "primary_toolbar" /* Variant.PRIMARY_TOOLBAR */;
}
#render() {
if (!this.#props.variant) {
throw new Error('Button requires a variant to be defined');
}
if (this.#isToolbarVariant()) {
if (!this.#props.iconUrl && !this.#props.iconName) {
throw new Error('Toolbar button requires an icon');
}
if (!this.#isEmpty) {
throw new Error('Toolbar button does not accept children');
}
}
if (this.#props.variant === "round" /* Variant.ROUND */) {
if (!this.#props.iconUrl && !this.#props.iconName) {
throw new Error('Round button requires an icon');
}
if (!this.#isEmpty) {
throw new Error('Round button does not accept children');
}
}
if (this.#props.iconName && this.#props.iconUrl) {
throw new Error('Both iconName and iconUrl are provided.');
}
const hasIcon = Boolean(this.#props.iconUrl) || Boolean(this.#props.iconName);
const classes = {
primary: this.#props.variant === "primary" /* Variant.PRIMARY */,
secondary: this.#props.variant === "secondary" /* Variant.SECONDARY */,
toolbar: this.#isToolbarVariant(),
'primary-toolbar': this.#props.variant === "primary_toolbar" /* Variant.PRIMARY_TOOLBAR */,
round: this.#props.variant === "round" /* Variant.ROUND */,
'text-with-icon': hasIcon && !this.#isEmpty,
'only-icon': hasIcon && this.#isEmpty,
small: Boolean(this.#props.size === "SMALL" /* Size.SMALL */ || this.#props.size === "TINY" /* Size.TINY */),
tiny: Boolean(this.#props.size === "TINY" /* Size.TINY */),
active: this.#props.active,
'explicit-size': Boolean(this.#props.iconHeight || this.#props.iconWidth),
};
const spinnerClasses = {
primary: this.#props.variant === "primary" /* Variant.PRIMARY */,
secondary: this.#props.variant === "secondary" /* Variant.SECONDARY */,
disabled: Boolean(this.#props.disabled),
'spinner-component': true,
};
// clang-format off
LitHtml.render(LitHtml.html `
<button title=${LitHtml.Directives.ifDefined(this.#props.title)} .disabled=${this.#props.disabled} class=${LitHtml.Directives.classMap(classes)}>
${hasIcon ? LitHtml.html `<${IconButton.Icon.Icon.litTagName}
.data=${{
iconPath: this.#props.iconUrl,
iconName: this.#props.iconName,
color: 'var(--color-background)',
width: this.#props.iconWidth || undefined,
height: this.#props.iconHeight || undefined,
}}
>
</${IconButton.Icon.Icon.litTagName}>` : ''}
${this.#props.spinner ? LitHtml.html `<span class=${LitHtml.Directives.classMap(spinnerClasses)}></span>` : ''}
<slot =${this.#onSlotChange}></slot>
</button>
`, this.#shadow, { host: this });
// clang-format on
}
// Based on https://web.dev/more-capable-form-controls/ to make custom elements form-friendly.
// Form controls usually expose a "value" property.
get value() {
return this.#props.value || '';
}
set value(value) {
this.#props.value = value;
}
// The following properties and methods aren't strictly required,
// but browser-level form controls provide them. Providing them helps
// ensure consistency with browser-provided controls.
get form() {
return this.#internals.form;
}
get name() {
return this.getAttribute('name');
}
get type() {
return this.#props.type;
}
get validity() {
return this.#internals.validity;
}
get validationMessage() {
return this.#internals.validationMessage;
}
get willValidate() {
return this.#internals.willValidate;
}
checkValidity() {
return this.#internals.checkValidity();
}
reportValidity() {
return this.#internals.reportValidity();
}
}
ComponentHelpers.CustomElements.defineComponent('devtools-button', Button);
//# sourceMappingURL=Button.js.map