import { LitElement, html, css } from 'lit'; import { property } from 'lit/decorators.js'; /** * Props interface for FlexContainer component * * @csspart ag-flex-container - The main flex container element * * @cssproperty --flex-direction - Controls the direction of flex items * @cssproperty --flex-wrap - Controls whether flex items wrap * @cssproperty --flex-justify - Controls alignment along main axis * @cssproperty --flex-align - Controls alignment along cross axis * @cssproperty --flex-align-content - Controls space between lines in multi-line flex containers * @cssproperty --flex-gap - Controls spacing between flex items (set via CSS, not via prop) */ export interface FlexContainerProps { /** Main axis direction (defaults to 'row') */ direction?: 'row' | 'row-reverse' | 'column' | 'column-reverse'; /** Whether flex items wrap (defaults to 'nowrap') */ wrap?: 'nowrap' | 'wrap' | 'wrap-reverse'; /** Alignment along main axis (defaults to 'flex-start') */ justify?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'; /** Alignment along cross axis (defaults to 'stretch') */ align?: 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch'; /** Space distribution between lines in multi-line containers (defaults to 'stretch') */ alignContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' | 'stretch'; /** Use inline-flex instead of flex (defaults to false) */ inline?: boolean; /** Reverse the direction (e.g., row becomes row-reverse, defaults to false) */ reverse?: boolean; /** Apply flex: 1 1 auto to all children for equal sizing (defaults to false) */ stretchChildren?: boolean; } /** Alias required for SDUI codegen discovery (glob: Flex/core/_*.ts → looks for FlexProps) */ export interface FlexProps extends FlexContainerProps {} export class FlexContainer extends LitElement implements FlexContainerProps { static styles = css` :host { display: var(--host-display, contents); } .flex-container { display: flex; flex-direction: var(--flex-direction, row); flex-wrap: var(--flex-wrap, nowrap); justify-content: var(--flex-justify, flex-start); align-items: var(--flex-align, stretch); align-content: var(--flex-align-content, stretch); gap: var(--flex-gap, var(--ag-space-0, 0)); } :host([inline]) .flex-container { display: inline-flex; } :host([stretch-children]) ::slotted(*) { flex: 1 1 auto; } /* Attribute-based CSS custom property defaults */ /* These set low-specificity defaults that can be easily overridden */ /* Direction */ :host([direction="row"]:not([reverse])) { --flex-direction: row; } :host([direction="row"][reverse]) { --flex-direction: row-reverse; } :host([direction="row-reverse"]:not([reverse])) { --flex-direction: row-reverse; } :host([direction="row-reverse"][reverse]) { --flex-direction: row; } :host([direction="column"]:not([reverse])) { --flex-direction: column; } :host([direction="column"][reverse]) { --flex-direction: column-reverse; } :host([direction="column-reverse"]:not([reverse])) { --flex-direction: column-reverse; } :host([direction="column-reverse"][reverse]) { --flex-direction: column; } /* Wrap */ :host([wrap="nowrap"]) { --flex-wrap: nowrap; } :host([wrap="wrap"]) { --flex-wrap: wrap; } :host([wrap="wrap-reverse"]) { --flex-wrap: wrap-reverse; } /* Justify */ :host([justify="flex-start"]) { --flex-justify: flex-start; } :host([justify="flex-end"]) { --flex-justify: flex-end; } :host([justify="center"]) { --flex-justify: center; } :host([justify="space-between"]) { --flex-justify: space-between; } :host([justify="space-around"]) { --flex-justify: space-around; } :host([justify="space-evenly"]) { --flex-justify: space-evenly; } /* Align */ :host([align="flex-start"]) { --flex-align: flex-start; } :host([align="flex-end"]) { --flex-align: flex-end; } :host([align="center"]) { --flex-align: center; } :host([align="baseline"]) { --flex-align: baseline; } :host([align="stretch"]) { --flex-align: stretch; } /* Align Content */ :host([align-content="flex-start"]) { --flex-align-content: flex-start; } :host([align-content="flex-end"]) { --flex-align-content: flex-end; } :host([align-content="center"]) { --flex-align-content: center; } :host([align-content="space-between"]) { --flex-align-content: space-between; } :host([align-content="space-around"]) { --flex-align-content: space-around; } :host([align-content="space-evenly"]) { --flex-align-content: space-evenly; } :host([align-content="stretch"]) { --flex-align-content: stretch; } /* Gap - Controlled ONLY via --flex-gap CSS custom property (no attribute/prop) */ /* Gap values are freeform strings, so attribute selectors are not feasible */ /* (CSS attr() doesn't work in custom properties and we'd need infinite selectors) */ /* Set --flex-gap via classes, ::part(), or inline styles. Default: var(--ag-space-0, 0) */ `; protected _direction: 'row' | 'row-reverse' | 'column' | 'column-reverse' = 'row'; protected _wrap: 'nowrap' | 'wrap' | 'wrap-reverse' = 'nowrap'; protected _justify: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' = 'flex-start'; protected _align: 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch' = 'stretch'; protected _alignContent: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' | 'stretch' = 'stretch'; @property({ type: String, reflect: true }) get direction(): FlexContainerProps['direction'] { return this._direction; } set direction(value: FlexContainerProps['direction']) { const valid = ['row', 'row-reverse', 'column', 'column-reverse']; this._direction = value && valid.includes(value) ? value : 'row'; this.requestUpdate(); } @property({ type: String, reflect: true }) get wrap(): FlexContainerProps['wrap'] { return this._wrap; } set wrap(value: FlexContainerProps['wrap']) { const valid = ['nowrap', 'wrap', 'wrap-reverse']; this._wrap = value && valid.includes(value) ? value : 'nowrap'; this.requestUpdate(); } @property({ type: String, reflect: true }) get justify(): FlexContainerProps['justify'] { return this._justify; } set justify(value: FlexContainerProps['justify']) { const valid = ['flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly']; this._justify = value && valid.includes(value) ? value : 'flex-start'; this.requestUpdate(); } @property({ type: String, reflect: true }) get align(): FlexContainerProps['align'] { return this._align; } set align(value: FlexContainerProps['align']) { const valid = ['flex-start', 'flex-end', 'center', 'baseline', 'stretch']; this._align = value && valid.includes(value) ? value : 'stretch'; this.requestUpdate(); } @property({ type: String, attribute: 'align-content' }) get alignContent(): FlexContainerProps['alignContent'] { return this._alignContent; } set alignContent(value: FlexContainerProps['alignContent']) { const valid = ['flex-start', 'flex-end', 'center', 'space-between', 'space-around', 'space-evenly', 'stretch']; this._alignContent = value && valid.includes(value) ? value : 'stretch'; this.requestUpdate(); } @property({ type: Boolean, reflect: true }) declare inline: boolean; @property({ type: Boolean, reflect: true }) declare reverse: boolean; @property({ type: Boolean, attribute: 'stretch-children', reflect: true }) declare stretchChildren: boolean; constructor() { super(); this.inline = false; this.reverse = false; this.stretchChildren = false; } updated(changedProperties: Map) { super.updated(changedProperties); // Note: Direction, wrap, justify, align, and alignContent are handled via // attribute selectors in the static styles. This keeps specificity low and // allows CSS custom properties to cascade naturally without specificity issues. // Gap is controlled solely via --flex-gap CSS custom property (not via attribute). // Users can override via classes, ::part(), or inline styles without needing !important. } render() { return html`
`; } }