*case.break
Summary
*case.break is a variant of *case used inside *switch or n-switch. It behaves like *case when matching against the switch value, and in addition it stops evaluating all later *case and *default branches in the same *switch block. It is effectively syntactic sugar for *case="expr" *break on the same element.
Alias:
*case.breakn-case.break
Both names share the same behavior.
Basic example
A simple status switch that does not fall through past the matching branch:
<serc-rod id="app" data='{"status": "ready"}'>
<div *switch="$data.status">
<p *case="'pending'">Pending...</p>
<p *case.break="'ready'">Ready</p>
<p *default>Unknown status</p>
</div>
</serc-rod>
Behavior:
- The host
<div *switch>evaluates$data.statusas the switch value. - The second branch
*case.break="'ready'"matches that value. - That branch is rendered.
- Because it is a
*case.breakbranch, Sercrod stops there and does not render the*defaultbranch.
Behavior
*case.break participates only as a child of a *switch or n-switch host:
- It is recognized only on direct element children of a node with
*switchorn-switch. - Outside of a
*switchcontext,*case.breakandn-case.breakare not interpreted by the switch logic and have no special effect.
Within a *switch block:
- Sercrod walks child elements in DOM order.
- It finds the first branch that should start rendering:
- A matching
*caseor*case.break. - Or a
*defaultwhen no earlier case has matched.
- A matching
- Once rendering has started, Sercrod is in a fallthrough mode:
- Every subsequent
*caseor*defaultbranch is also rendered, until a break-type directive is seen.
- Every subsequent
*case.breakis both:- A case branch (its expression is matched against the switch value).
- A break marker (it stops processing any later branches in this
*switch).
Conceptually:
*case="expr"means "start here ifexprmatches; then continue rendering later branches."*case.break="expr"means "start here ifexprmatches; render this branch; then stop, with no further fallthrough."
Evaluation timing
Within the host element that has *switch or n-switch:
-
Sercrod evaluates the switch expression once:
- It reads the attribute value from
*switchorn-switch. - It evaluates that expression and stores the result as the switch value.
- The value is then exposed to children as
$switch.
- It reads the attribute value from
-
Sercrod iterates over child elements in DOM order.
-
For each child:
- If it does not have
*case,n-case,*case.break,n-case.break,*default, orn-default, it is ignored by the switch logic and never rendered by this*switch. - If no branch has started yet:
*case/n-case/*case.break/n-case.break:- Sercrod evaluates the case expression and compares it to the switch value.
- If it matches, rendering starts from this branch.
*default/n-default:- If no earlier branch has matched, rendering starts from this default.
- If it does not have
-
Once a starting branch is chosen (including
*case.break):- Sercrod clones that branch, removes control attributes, and renders it with the augmented child scope (including
$switch). - It then continues in fallthrough mode (see below) unless a break is detected.
- Sercrod clones that branch, removes control attributes, and renders it with the augmented child scope (including
Case expression semantics
The expression given to *case.break is interpreted in the same way as for *case.
Given:
switchValas the evaluated switch value.rawas the attribute string from*case.break="raw".
Sercrod applies the following rules:
-
It first tries to evaluate the case expression as a normal Sercrod expression, with
$switchinjected into the scope:- If the result is a function, Sercrod calls
fn(switchVal, scope)and uses the truthiness of the return value. - If the result is a
RegExp, Sercrod tests it againstString(switchVal). - If the result is an array, Sercrod checks whether any element is strictly equal to
switchVal(usingObject.issemantics). - If the result is an object with a
hasmethod (for example aSet), Sercrod callsset.has(switchVal)and uses the result. - If the result is a boolean, the boolean itself decides the match.
- If the result is a string, number, or bigint, Sercrod compares it to
switchValusing strict identity. - For other result types, this step does not produce a match.
- If the result is a function, Sercrod calls
-
If evaluation throws, or if you intentionally keep the expression as a simple string, Sercrod falls back to a token list:
- It splits the raw string on commas or pipes:
"a|b,c"becomes tokens like"a","b","c". - It trims empty tokens.
- For each token
t:- It tries to evaluate
tas an expression. - If that fails, it uses
titself as a string literal. - It compares the result to
switchValusing strict identity.
- It tries to evaluate
- If any token matches, the case matches.
- It splits the raw string on commas or pipes:
Useful patterns:
-
Direct equality:
*case.break="'ready'"matches when$switchis strictly"ready".
-
Membership in a small set:
*case.break="'pending' | 'queued'"
Matches when$switchis"pending"or"queued".
-
Function-based matching:
*case.break="(val) => val > 0"
Matches if the function returns a truthy value when called withswitchValand the current scope.
-
Regular expression matching:
*case.break="/^ok-/"(assuming the expression is parsed to a RegExp)
Matches when the string form of$switchstarts withok-.
Scope and $switch
Inside a *switch block, and inside a *case.break branch:
-
The child scope is created as a shallow copy of the parent scope plus
$switch. -
The case expression for
*case.breakis evaluated with$switchavailable. -
The branch content is rendered with the same scope:
- All original data (for example
$data,$root,$parent) remain available. $switchholds the value of the*switchexpression.*case.breakitself does not create new variables; it only controls whether and where this branch starts and whether execution stops afterward.
- All original data (for example
You can freely refer to $switch inside the branch body:
<div *switch="status">
<p *case.break="'error'">
Error: <span *print="$switch"></span>
</p>
<p *default>OK</p>
</div>
Relationship to *case, *default and *break
Within a *switch block:
-
*caseandn-case:- Define branches that start rendering when their expression matches
$switch. - Do not stop fallthrough by themselves.
- Define branches that start rendering when their expression matches
-
*defaultandn-default:- Define a branch that starts rendering only if no earlier
*caseor*case.breakhas matched. - Do not stop fallthrough by themselves.
- Define a branch that starts rendering only if no earlier
-
*breakandn-breakon a branch:- If placed together with
*caseor*default, they cause the switch to stop after rendering that branch. - This is equivalent to "case + break" or "default + break" in JavaScript.
- If placed together with
-
*case.breakandn-case.break:- Combine
*caseand*breakinto a single directive. - They match using the same rules as
*case. - When the branch is rendered, they also act as a break marker, stopping the switch from evaluating later branches.
- Combine
Syntactic equivalence:
-
The following two branches are equivalent in behavior:
-
*case.break="expr" -
*case="expr" *break
-
Sercrod implementation treats both forms in the same way:
- For matching, it uses the
*case/n-case/*case.break/n-case.breakexpression. - For breaking, it checks for
*break,n-break,*case.break, orn-case.breakon the original branch element.
Fallthrough and break behavior
*switch in Sercrod uses a DOM-ordered, fallthrough model similar to JavaScript:
- Sercrod locates the first branch that should start rendering.
- From that branch onward, it renders every subsequent
*case/*defaultbranch until a break is detected. - A break is detected when the original branch element has any of:
*breakn-break*case.breakn-case.break
*case.break is therefore the most concise way to express:
- "Start rendering here when this condition holds."
- "Do not allow any further fallthrough."
Example showing fallthrough vs break:
<div *switch="state">
<p *case="'warm'">Warm</p>
<p *case="'hot'">Hot</p>
<p *default>Default</p>
</div>
<div *switch="state">
<p *case="'warm'">Warm</p>
<p *case.break="'hot'">Hot only</p>
<p *default>Default</p>
</div>
- If
stateis"hot":- In the first block:
- The
*case="'hot'"branch starts rendering. - There is no break, so the
*defaultbranch also renders (fallthrough).
- The
- In the second block:
- The
*case.break="'hot'"branch starts rendering and also stops the switch. - The
*defaultbranch does not render.
- The
- In the first block:
Best practices
-
Use
*case.breakwhen you explicitly want "no fallthrough":- It is clearer than writing
*case="expr" *break. - It makes it obvious that this branch is terminal inside the switch.
- It is clearer than writing
-
Keep case expressions simple:
- Prefer direct comparisons or small membership sets.
- Move complex logic to a helper function and call it from the case expression.
-
Use
$switchconsistently:- When writing more advanced logic, prefer using
$switchinside the expression rather than repeating the switch expression. - For example,
*case.break="(v) => v > 100"can be easier to read than rewriting the whole expression.
- When writing more advanced logic, prefer using
-
Avoid redundant
*breakattributes:*case.break="expr" *breakis allowed but redundant; the break is already implied by*case.break.- For clarity, choose either
*case="expr" *breakor*case.break="expr", not both.
-
Do not rely on non-case children inside
*switch:- Elements without any of
*case,n-case,*case.break,n-case.break,*default, orn-defaultare ignored by the switch logic. - Wrap branch content in dedicated case/default elements.
- Elements without any of
Additional examples
Simple status mapping with no fallthrough:
<serc-rod id="status-app" data='{"status":"error"}'>
<div *switch="$data.status">
<p *case="'ok'">All good</p>
<p *case.break="'error'">Something went wrong</p>
<p *default>Unknown status: <span *print="$switch"></span></p>
</div>
</serc-rod>
Using a function as a case expression:
<serc-rod id="range-switch" data='{"value": 42}'>
<div *switch="$data.value">
<p *case="(v) => v < 0">Negative</p>
<p *case="(v) => v === 0">Zero</p>
<p *case.break="(v) => v > 0">Positive (and stop)</p>
<p *default>Unreachable default</p>
</div>
</serc-rod>
Membership via list syntax:
<serc-rod id="role-switch" data='{"role":"admin"}'>
<div *switch="$data.role">
<p *case="'guest' | 'anonymous'">Guest mode</p>
<p *case.break="'user' | 'admin'">Signed-in user</p>
<p *default>Other role: <span *print="$switch"></span></p>
</div>
</serc-rod>
Notes
*case.breakandn-case.breakare only meaningful under a*switchorn-switchhost.- The case expression of
*case.breakis evaluated with access to$switchand all outer scope variables. *case.breakuses exactly the same matching rules as*case; it only differs by also acting as a break.- A branch with
*case.breakwill stop fallthrough even if it appears before*default, so that default branch will not run. - For loop control inside
*foror*each, use*breakrather than*case.break; they are separate directives with different purposes.