Skip to content

Commit

Permalink
feat(if): add *else directive (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
lowlighter authored Oct 8, 2024
1 parent 9c5daea commit 6c17d9b
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 1 deletion.
2 changes: 1 addition & 1 deletion @mizu/if/deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"version": "0.1.0",
"exports": {
".": "./mod.ts",
//"./else": "./else/mod.ts"
"./else": "./else/mod.ts"
}
}
14 changes: 14 additions & 0 deletions @mizu/if/else/mod.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<mizu-directive id="else" directory="if/else">
<code #name><span class="hljs-keyword">*else</span><wbr /><span class="muted">="expression"</span></code>
<p #description>
Conditionally render an element after another <a href="#if"><code class="hljs-keyword">*if</code></a> or <a href="#else"><code class="hljs-keyword">*else</code></a> directive.
</p>
<code #example *skip>
<div *if="false"></div>
<div *else="false"></div>
<div *else><!--...--></div>
</code>
<mizu-restriction #note>
Must be defined on an element immediately preceded by another element with either a <a href="#if"><code class="hljs-keyword">*if</code></a> or <a href="#else"><code class="hljs-keyword">*else</code></a> directive.
</mizu-restriction>
</mizu-directive>
38 changes: 38 additions & 0 deletions @mizu/if/else/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Imports
import { type Directive, Phase } from "@mizu/mizu/core/engine"
import { _if } from "@mizu/if"
export type * from "@mizu/mizu/core/engine"

/** `*else` directive. */
export const _else = {
name: "*else",
phase: Phase.TOGGLE,
default: "true",
execute(renderer, element, { attributes: [attribute] }) {
let previous = element.previousSibling as HTMLElement
while (previous) {
// Break on non-empty text nodes
if ((previous.nodeType === renderer.window.Node.TEXT_NODE) && (previous.textContent?.trim())) {
break
}

// Force directive to `false` when a previous operand is truthy
if (previous.nodeType === renderer.window.Node.ELEMENT_NODE) {
if (renderer.getAttributes(previous, [_if.name, _else.name] as string[], { first: true })) {
return _if.execute(renderer, element, { ...arguments[2], _directive: { directive: this.name, expression: attribute.value, value: "false" } })
}
break
}

// Execute directive with given expression when first operand is found and is falsy (meaning all previous operand were falsy too)
if ((previous.nodeType === renderer.window.Node.COMMENT_NODE) && (renderer.getAttributes(renderer.cache("*").get(previous), _if.name, { first: true }))) {
return _if.execute(renderer, element, { ...arguments[2], _directive: { directive: this.name, expression: attribute.value, value: attribute.value || this.default } })
}
previous = previous.previousSibling as HTMLElement
}
renderer.warn(`[${this.name}] must be immediately preceded by another [${_if.name}] or [${_else.name}], ignoring`, element)
},
} as Directive

/** Default exports. */
export default [_if, _else]
137 changes: 137 additions & 0 deletions @mizu/if/else/mod_test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<load directives="@mizu/if/else"></load>

<test name="[*else] comments out elements when a previous conditional directive is truthy">
<render>
<p *if="true">foo</p>
<p *else="true">bar</p>
<p *else>baz</p>
</render>
<expect>
<p>foo</p>
<!--[*else="true"]-->
<!--[*else=""]-->
</expect>
<render>
<p *if="false">foo</p>
<p *else="true">bar</p>
<p *else>baz</p>
</render>
<expect>
<!--[*if="false"]-->
<p>bar</p>
<!--[*else=""]-->
</expect>
</test>

<test name="[*else] renders elements when all previous conditional directives are falsy">
<render>
<p *if="false">foo</p>
<p *else="false">bar</p>
<p *else="true">baz</p>
<p *else>qux</p>
</render>
<expect>
<!--[*if="false"]-->
<!--[*else="false"]-->
<p>baz</p>
<!--[*else=""]-->
</expect>
</test>

<test name="[*else] is truthy when empty">
<render>
<p *if="false">foo</p>
<p *else>bar</p>
</render>
<expect>
<!--[*if="false"]-->
<p>bar</p>
</expect>
</test>

<test name="[*else] is reactive">
<script>
context.value = 0
</script>
<render>
<p *if="value === 0">foo</p>
<p *else="value === 1">bar</p>
<p *else>baz</p>
</render>
<expect>
<p>foo</p>
<!--[*else="value === 1"]-->
<!--[*else=""]-->
</expect>
<script>
context.value = 1
</script>
<render></render>
<expect>
<!--[*if="value === 0"]-->
<p>bar</p>
<!--[*else=""]-->
</expect>
<script>
context.value = 2
</script>
<render></render>
<expect>
<!--[*if="value === 0"]-->
<!--[*else="value === 1"]-->
<p>baz</p>
</expect>
<script>
context.value = 1
</script>
<render></render>
<expect>
<!--[*if="value === 0"]-->
<p>bar</p>
<!--[*else=""]-->
</expect>
<script>
context.value = 0
</script>
<render>
<p *if="value === 0">foo</p>
<p *else="value === 1">bar</p>
<p *else>baz</p>
</render>
<expect>
<p>foo</p>
<!--[*else="value === 1"]-->
<!--[*else=""]-->
</expect>
</test>

<test name="[*else] (error) must be immediately preceded by either a [*if] or another [*else] directive">
<render>
<p *else>foo</p>
<p *else>bar</p>
</render>
<expect>
<p *warn="[*else] must be immediately preceded by another [*if] or [*else], ignoring">foo</p>
<!--[*else=""]-->
</expect>
<render>
<p *if="false">foo</p>
<hr />
<p *else>bar</p>
</render>
<expect>
<!--[*if="false"]-->
<hr />
<p *warn="[*else] must be immediately preceded by another [*if] or [*else], ignoring">bar</p>
</expect>
<render>
<p *if="false">foo</p>
non-empty text node
<p *else>bar</p>
</render>
<expect>
<!--[*if="false"]-->
non-empty text node
<p *warn="[*else] must be immediately preceded by another [*if] or [*else], ignoring">bar</p>
</expect>
</test>
1 change: 1 addition & 0 deletions @mizu/if/else/mod_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
await import("@mizu/mizu/core/testing").then(({ test }) => test(import.meta))

0 comments on commit 6c17d9b

Please sign in to comment.