The ConditionEditor components lay out editable condition rows — a card per condition holding the controls that edit it, joined to the rows around it by their operator.
Stack ConditionEditorItems in a ConditionEditor. An item's children are its first line of
controls: pass them directly, without a stack of your own around them. Give every row after the
first an operator.
operator is plain text, shown as a Badge between the row and the one before it. Write it in
sentence case ("And", "Or") and leave it unset on the first row.
actions holds what acts on the whole condition — duplicate, remove — at the trailing edge of the
card. details holds what goes below the first line: a refinement, filter rows, an "Add filter"
button. Lay out the content of details yourself.
heading puts a caption above the controls, inside the card: what kind of condition the row is
("Object attribute"), with an icon in a small Coin if you like. The handle, the connector and the actions line
up with the heading instead of the controls. The heading also names the row's controls for screen
readers.
When one field picker covers several kinds of field, derive the heading from the chosen field, so picking a field of another kind relabels the row instead of needing a separate type control.
A set takes a header above its rows, headerActions at the header's trailing edge, and a footer
below its rows. Use them for a group: the header says how its conditions combine, with the control
that changes it; the header's actions duplicate or remove the group; the footer adds to it. The
header spans the group's full width, out past the cards and the rows' actions, so it reads as
governing the rows; the footer lines up with the cards. A set with a header is announced as a group
named by it.
When the header sets how the group matches, keep the operator labels between its rows read-only so there's one place to change it.
footer is an open slot at the foot of the card, below the controls and the details. Put whatever
the row needs there (an example of what it matches, a link, a note), composed from Pluma
components; its pieces sit in a row that starts where the controls start. Pluma doesn't style
what's inside, so pick the text style yourself.
Every form control and Button in a row lines up on the same center line, and an error appears
below its own control without moving the rest of the row. Don't pass withCenterBaseline to them
yourself. Wrap other inline content, like an icon, in CenterBaseline to line it up with the
controls.
withHandle renders a "Reorder condition" button beside the card, and isHandleDisabled disables
it. Only the handle starts a drag, never the card, so the controls inside a row stay usable. The
handle doesn't drag on its own: give its element to your drag-and-drop library with handleRef. In
React, pass any ref — dnd-kit's handleRef works as is. In Ember, pass a callback; it's called
with the button when it renders and with null when it's removed.
Pluma doesn't ship or require a drag-and-drop library. It styles the row in flight — a ring and a
fade on its card — for as long as the library marks the handle aria-pressed or aria-grabbed, as
dnd-kit does for both pointer and keyboard drags. While a row is dragged, mark the row it would land
next to with dropIndicator to draw the line where it will go. The examples
below use dnd-kit with its live preview turned off, so the rows hold
still and the line shows where the dragged row will land. Drag a row by its handle.
Your drag-and-drop library is also responsible for keyboard reordering and for announcing moves. See Accessibility.
dropIndicator draws the line that shows where a dragged row will land: 'before' the row or
'after' it. Your application decides which row and which side, and whether a move is allowed;
Pluma only draws the line. A slot between two rows draws the same line whichever side marks it, so
the line doesn't jump as the pointer crosses from one row to the next. Between rows joined by an
operator, it runs through the operator strip, level with the operator, starting just after it. The
line is trimmed short of the card on both sides and starts at the card's own edge (after the
operator, where there is one), so in a nested set its indent shows which group the row lands in. It takes no space, so the rows don't move when it appears.
The prop sets data-drop-indicator on the row. A drag layer can set that attribute directly
instead, to move the line without re-rendering the rows.
In a virtualized list, where each row sits in its own wrapper, mark the row below a slot
('before') rather than the row above it: a row can't see past its own wrapper to tell what follows
it.
Pass operatorOptions and onOperatorChange to make a row's operator a control. The label becomes
a small SegmentedControl of the options: an operator is a choice of two, and two options don't need a
menu to hide one of them, so both show and changing it is one click. Picking one calls
onOperatorChange with its value; operator is the chosen option's value. Use a
SegmentedControl for a group's All / Any match in its header too, so the two read the same. Without
both props, the operator stays the read-only label, so each row can be switchable or not, and only
a switchable operator renders as a control. On a connector, an operator (the control or the
read-only label) sits in line with the cards, the way a row does, with a branch from the connector
like the ones into the cards, so the cards keep the narrow gutter. Each row's operator is its own: Pluma draws the operators it's given and reports
which row changed, and your application decides what the change means.
Rows in one set read as one expression, so a set that mixes operators is ambiguous: And, then Or,
across three rows could mean (plan && company) || email or plan && (company || email). To show
which, nest a ConditionEditor for the part that binds tighter.
Any row's operator can be a control, and any number of rows in a set can have one; each changes its own row alone. A control between two rows reads as joining those two, so changing it shouldn't change any other row:
header (an All / Any
SegmentedControl, as under Header and footer) and show the operators between rows read-only. The
header visibly belongs to the whole set.The example below has a two-row set at each level, so each set's one operator is its second row's.
Put a ConditionEditor inside an item to nest a set of conditions. The nested set is drawn as a
group; there's no prop for it.
ConditionEditor expects to sit on surface-content. On any other background, set the
connectorSurfaceColor CSS variable from @customerio/pluma-components/css to that background.
Nested groups alternate their surface by depth, however deep they go: a group on the top-level set
is surface-block, a group inside that surface-recessed, the next surface-block again, and so
on. Set groupBackgroundColor to use one surface at
every depth.
Leave connectorPosition unset when rows render side by side in a ConditionEditor. In a
virtualized list, where each row renders in its own wrapper and only some are rendered, set it on
every row: 'first', 'middle' or 'last' by the row's index in the whole set, or 'only' for a
set of one.
A named block can't be wrapped in {{#if}}. Write <:heading>, <:details>, <:footer> and <:actions> once
and drive them with hasHeadingBlock, hasDetailsBlock, hasFooterBlock and hasActionsBlock: leave one undefined and its slot renders
whenever it has content; set it and it wins.
PlumaConditionEditor extends BoxDefault:'div'
What element or component should be rendered.
Explicitly controls whether the header's actions render. Leave undefined to render them whenever they have content.
Explicitly controls whether the header renders. Leave undefined to render it whenever it has content.
A line above the rows describing the set: how its conditions combine ("Any of the following conditions match"), with the control that changes it. It lines up with the cards and names the set for assistive technology. What the control is and what it means are up to your application.
Buttons at the trailing edge of the header, such as duplicate or remove the group. They line up with the rows' actions.
PlumaConditionEditorItem extends BoxTrailing slot inside the card (duplicate, remove, status). Each child is centered on the first line of controls.
Where the row sits on its set's connector. Leave it unset and the
connector is worked out from the rows rendered beside it. Set it when the
rows aren't rendered as siblings — in a virtualized list, each row is
alone in its own positioned wrapper and only some are rendered — so the
connector still runs through the set: 'first', 'middle' or 'last',
or 'only' for a set of one, which has no connector.
Content stacked below the first line of controls: refinements, filter rows, an "add filter" button.
Draws the line that shows where a dragged row will land: 'before' this
row or 'after' it. The line falls on the same pixels whichever side of
a slot marks it, and starts at the card's leading edge, so its indent
shows which set the row lands in. It sets data-drop-indicator on the
row, which a drag layer can also set directly to move the line without
re-rendering.
Receives the handle's button when it renders, and null when it's
removed — the element to give a drag-and-drop library as its handle. In
React, any ref works, so dnd-kit's handleRef can be passed directly.
Explicitly controls whether the actions slot renders. Leave undefined to render it whenever it has content.
Explicitly controls whether the details slot renders. Leave undefined to render it whenever it has content.
Explicitly controls whether the heading slot renders. Leave undefined to render it whenever it has content.
A caption above the controls, inside the card: what kind of condition the row is ("Object attribute"), with an icon if you like. The handle, the connector's branch and the actions meet this line instead of the controls, and it names the row's controls for assistive technology.
Default:false
Disables the reorder handle while keeping it visible.
Called with the option's value when one is picked from operatorOptions.
Text for the label floating in the gap above the card — the operator that joins this row to the one before it, in sentence case ("And", "Or"). Rendered as a Badge, the same one ConditionViewer group labels use. Leave it unset on the first row.
Makes the operator a control, together with onOperatorChange: the label
becomes a small Select of these options, the same control as a group's
match and the row's own fields, and picking one calls onOperatorChange. Without both, the operator is the read-only label,
so each row can be switchable or not. operator is then the
chosen option's value, and the label reads its label. The change is this
row's alone; whether it relabels any other row is up to your application.
Rows in one set read as one expression, so mixed operators in a set are
ambiguous: nest a ConditionEditor for the part that binds tighter.
Default:false
Renders the reorder handle beside the card. The handle doesn't drag on
its own: pass its element to a drag-and-drop library with handleRef.