@carbon/ibm-products-web-components
Version:
Carbon for IBM Products Web Components
274 lines (272 loc) • 10.4 kB
JavaScript
/**
* 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