UNPKG

@carbon/ibm-products-web-components

Version:
274 lines (272 loc) 10.4 kB
/** * Copyright IBM Corp. 2020, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import "../../globals/settings.js"; import action_set_default$1 from "./action-set.scss.js"; import pconsole_default from "../../globals/internal/pconsole.js"; import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.127.0/helpers/decorate.js"; import { classMap } from "lit/directives/class-map.js"; import { LitElement, html } from "lit"; import { property, state } from "lit/decorators.js"; import { ref } from "lit/directives/ref.js"; import { carbonElement } from "@carbon/web-components/es/globals/decorators/carbon-element"; import '@carbon/web-components/es-custom/components/button/index.js'; //#region src/components/action-set/action-set.ts /** * @license * * Copyright IBM Corp. 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ const blockClass = `c4p--action-set`; const ButtonSizes = [ "sm", "md", "lg", "xl", "2xl" ]; const defaultKind = "primary"; /** * Determines if buttons should be stacked based on size and number of actions */ const willStack = (size, numberOfActions) => size === "sm" || size === "md" && numberOfActions > 2; /** * Returns the order priority for a button kind * Lower numbers appear first (or last when stacking) */ const buttonOrder = (kind) => ({ ghost: 1, "danger--ghost": 2, tertiary: 3, danger: 5, primary: 6 })[kind] ?? 4; /** * Sorts actions based on button kind and stacking mode * When not stacking: ghost first, primary last * When stacking: primary first, ghost last */ const sortActions = (actions, stacking) => { const sortedActions = [...actions]; sortedActions.sort((action1, action2) => (buttonOrder(action1.kind || defaultKind) - buttonOrder(action2.kind || defaultKind)) * (stacking ? -1 : 1)); return sortedActions; }; /** * Validates action set configuration and returns problems */ const validateActionSet = (actions, size) => { if (!actions || actions.length === 0) return []; const problems = []; const stacking = willStack(size, actions.length); const countActions = (kind) => actions.filter((action) => (action.kind || defaultKind) === kind).length; const primaryActions = countActions("primary"); const secondaryActions = countActions("secondary"); const tertiaryActions = countActions("tertiary"); const dangerActions = countActions("danger"); const ghostActions = countActions("ghost") + countActions("danger--ghost"); if (stacking && actions.length > 3) problems.push("you cannot have more than three actions in this size of ActionSet"); if (actions.length > 4) problems.push("you cannot have more than four actions in an ActionSet"); if (primaryActions > 1) problems.push("you cannot have more than one 'primary' action in an ActionSet"); if (ghostActions > 1) problems.push("you cannot have more than one 'ghost' action in an ActionSet"); if (stacking && actions.length > 1 && ghostActions > 0) problems.push("you cannot have a 'ghost' button in conjunction with other action types in this size of ActionSet"); if (actions.length > primaryActions + secondaryActions + tertiaryActions + dangerActions + ghostActions) problems.push("you can only have 'primary', 'danger', 'secondary', 'tertiary', 'ghost' and 'danger--ghost' buttons in an ActionSet"); return problems; }; let CDSActionSet = class CDSActionSet extends LitElement { constructor(..._args) { super(..._args); this._slottedButtonCount = 0; this.size = "md"; this.disableStacking = false; this.actions = []; this._hideSiblingMargin = () => { let items = []; const slot = this.shadowRoot?.querySelector("slot"); if (slot) items = slot.assignedElements().filter((el) => el.tagName.toLowerCase() === `cds-custom-button`); if (items.length === 0 && this.shadowRoot) items = Array.from(this.shadowRoot.querySelectorAll(`cds-custom-button`)); if (items.length === 0) return; const focusedIndex = items.findIndex((el) => el.matches(":focus-within")); items.forEach((el, idx) => { const shouldHide = focusedIndex >= 0 && (idx === focusedIndex || idx === focusedIndex + 1); el.toggleAttribute("hide-margin", shouldHide); }); }; } /** * Computed property: `true` if the buttons are currently stacked. * This is derived from size, disableStacking, and button count. */ get stacked() { const buttonCount = this.actions?.length || this._slottedButtonCount; return this.disableStacking ? false : willStack(this.size, buttonCount); } static { this.styles = action_set_default$1; } /** * Applies common button styling (size, classes, expressive attribute) * @private */ _applyButtonStyles(button, kind) { if (this.buttonSize) button.setAttribute("size", this.buttonSize); button.classList.add(`${blockClass}__action-button`); if (kind === "ghost" || kind === "danger--ghost") button.classList.add(`${blockClass}__action-button--ghost`); button.setAttribute("is-expressive", "true"); } /** * Processes actions: validates, determines stacking, and sorts * @private */ _processActions(actions) { const problems = validateActionSet(actions, this.size); const stacking = this.disableStacking ? false : willStack(this.size, actions.length); const sortedActions = sortActions(actions, stacking); if (problems.length > 0) pconsole_default.warn(`Invalid actions in ActionSet: ${problems.join(", and ")}.`); return { sortedActions, stacking, problems }; } /** * Handler for @slotchange, processes and orders buttons based on ActionSet logic * * @private */ _handleSlotChange(event) { const childItems = event.target.assignedNodes().filter((elem) => elem.matches !== void 0 ? elem.matches(`cds-custom-button`) : false); if (childItems.length === 0) return; const actions = childItems.map((button) => { const kind = button.getAttribute("kind"); const disabled = button.hasAttribute("disabled"); const label = button.textContent?.trim() || ""; return { kind: kind || "primary", disabled, label }; }); const { sortedActions } = this._processActions(actions); this._slottedButtonCount = childItems.length; this._reorderButtons(childItems, sortedActions); const update = new CustomEvent(`c4p-action-set-update`, { bubbles: true, cancelable: true, composed: true }); this.dispatchEvent(update); } /** * Reorders the button elements in the DOM based on the sorted actions * and applies appropriate styling * * @private */ _reorderButtons(buttons, sortedActions) { const buttonMap = /* @__PURE__ */ new Map(); buttons.forEach((button) => { const label = button.textContent?.trim() || ""; buttonMap.set(label, button); }); sortedActions.forEach((action) => { const button = buttonMap.get(action.label || ""); if (button) this._applyButtonStyles(button, action.kind); }); } connectedCallback() { super.connectedCallback(); this.addEventListener("focusin", this._hideSiblingMargin); this.addEventListener("focusout", this._hideSiblingMargin); } disconnectedCallback() { super.disconnectedCallback(); this.removeEventListener("focusin", this._hideSiblingMargin); this.removeEventListener("focusout", this._hideSiblingMargin); } /** * Renders buttons from the actions prop * @private */ _renderActionsFromProp() { if (!this.actions || this.actions.length === 0) return null; const { sortedActions } = this._processActions(this.actions); return sortedActions.map((action) => { const { kind = "primary", label = "", disabled = false, loading = false, onClick, class: customClass, ...rest } = action; const buttonClasses = classMap({ [`${blockClass}__action-button`]: true, [`${blockClass}__action-button--ghost`]: kind === "ghost" || kind === "danger--ghost", ...customClass ? { [customClass]: true } : {} }); const buttonRef = (el) => { if (el && rest) Object.entries(rest).forEach(([key, value]) => { if (value !== void 0 && value !== null) if (typeof value === "boolean") if (value) el.setAttribute(key, ""); else el.removeAttribute(key); else el.setAttribute(key, String(value)); }); }; return html` <cds-custom-button ${ref(buttonRef)} class="${buttonClasses}" kind="${kind}" size="${this.buttonSize}" ?disabled="${disabled || loading}" is-expressive="true" @click="${typeof onClick === "function" ? onClick : () => {}}" > ${label} </cds-custom-button> `; }); } render() { const { actions, _slottedButtonCount, stacked } = this; const buttonCount = actions?.length || _slottedButtonCount; const defaultClasses = { [blockClass]: true, [`${blockClass}--row-single`]: !stacked && buttonCount === 1, [`${blockClass}--row-double`]: !stacked && buttonCount === 2, [`${blockClass}--row-triple`]: !stacked && buttonCount === 3, [`${blockClass}--row-quadruple`]: !stacked && buttonCount >= 4, [`${blockClass}--stacking`]: stacked, [`${blockClass}--${this.size}`]: true }; if (actions && actions.length > 0) return html`<div class="${classMap({ ...defaultClasses, [`cds-custom--btn-set--stacked`]: stacked, [`cds-custom--btn-set`]: true })}" part="action-set" role="list" > ${this._renderActionsFromProp()} </div>`; return html`<div class="${classMap({ ...defaultClasses, [`cds-custom--btn-set--stacked`]: stacked, [`cds-custom--btn-set`]: true })}" part="action-set" role="list"> <slot @slotchange="${this._handleSlotChange}"></slot> </div>`; } }; __decorate([state()], CDSActionSet.prototype, "_slottedButtonCount", void 0); __decorate([property({ attribute: "button-size" })], CDSActionSet.prototype, "buttonSize", void 0); __decorate([property()], CDSActionSet.prototype, "size", void 0); __decorate([property({ type: Boolean, attribute: "disable-stacking" })], CDSActionSet.prototype, "disableStacking", void 0); __decorate([property({ type: Array })], CDSActionSet.prototype, "actions", void 0); CDSActionSet = __decorate([carbonElement(`c4p-action-set`)], CDSActionSet); var action_set_default = CDSActionSet; //#endregion export { ButtonSizes, CDSActionSet, action_set_default as default }; //# sourceMappingURL=action-set.js.map