Skip to content

Commit 48f83cd

Browse files
committed
feat: add conditional textareas and section separators to Interactive Code component
1 parent 35665b8 commit 48f83cd

6 files changed

Lines changed: 311 additions & 39 deletions

File tree

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import '@softwarity/interactive-code';
5555
| Attribute | Type | Description |
5656
|-----------|------|-------------|
5757
| `language` | `'html' \| 'scss' \| 'typescript' \| 'shell'` | Syntax highlighting language |
58+
| `show-separators` | `boolean` | Show visual separators between textarea sections |
5859

5960
| Property | Type | Description |
6061
|----------|------|-------------|
@@ -184,6 +185,25 @@ Toggle HTML attributes on/off. Supports attributes with or without values:
184185
</interactive-code>
185186
```
186187

188+
### Conditional Textareas
189+
190+
Show different code sections based on binding values. Multiple `<textarea>` elements are concatenated, and the `condition` attribute controls visibility:
191+
192+
```html
193+
<interactive-code language="typescript" show-separators>
194+
<textarea>const result = provider.complete(input, { groupBy: ${groupBy} });</textarea>
195+
<textarea condition="!groupBy">// Use result.items for flat list
196+
console.log(result.items);</textarea>
197+
<textarea condition="groupBy">// Use result.groups for grouped display
198+
console.log(result.groups);</textarea>
199+
<code-binding key="groupBy" type="select" options="undefined,'continent'" value="undefined"></code-binding>
200+
</interactive-code>
201+
```
202+
203+
- `condition="key"` - Show when binding value is truthy
204+
- `condition="!key"` - Show when binding value is falsy
205+
- `show-separators` - Add visual separators between sections (customizable via `--code-separator-color`)
206+
187207
## CSS Customization
188208

189209
The component supports CSS custom properties for styling:
@@ -192,11 +212,14 @@ The component supports CSS custom properties for styling:
192212
|----------|---------|-------------|
193213
| `--code-bg` | `#1e1e1e` | Background color of the code block |
194214
| `--code-border-radius` | `8px` | Border radius of the code block |
215+
| `--code-separator-color` | `rgba(255, 255, 255, 0.1)` | Color of separators between textarea sections |
216+
| `--code-editable-underline` | `#4ec9b0` | Color of wavy underline on editable values |
195217

196218
```css
197219
interactive-code {
198220
--code-bg: #282c34;
199221
--code-border-radius: 4px;
222+
--code-separator-color: rgba(100, 100, 100, 0.5);
200223
}
201224
```
202225

RELEASE_NOTES.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@
22

33
## 1.0.6
44

5+
### Features
6+
7+
- **Conditional textareas**: Multiple `<textarea>` elements can now have a `condition` attribute to conditionally show/hide code sections based on binding values
8+
- `condition="key"` → show when binding is truthy
9+
- `condition="!key"` → show when binding is falsy
10+
- Useful for showing different code patterns based on user selection
11+
- **Section separators**: Optional `show-separators` attribute on `<interactive-code>` adds subtle visual separators between concatenated textarea sections
12+
- Customizable via `--code-separator-color` CSS variable
13+
14+
### Example
15+
16+
```html
17+
<interactive-code language="typescript" show-separators>
18+
<textarea>const result = provider.complete(input, { groupBy: ${groupBy} });</textarea>
19+
<textarea condition="!groupBy">// Use result.items for flat list</textarea>
20+
<textarea condition="groupBy">// Use result.groups for grouped display</textarea>
21+
<code-binding key="groupBy" type="select" options="undefined,'continent'" value="undefined"></code-binding>
22+
</interactive-code>
23+
```
24+
25+
### Tests
26+
27+
- Added 7 new tests for conditional textareas and separators (110 tests total)
28+
529
---
630

731
## 1.0.5

demo/index.html

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,38 @@ <h4 style="margin: 0 0 0.5rem 0;">Title</h4>
785785
</div>
786786
</div>
787787
</article>
788+
789+
<!-- Conditional Textareas -->
790+
<article class="doc-section">
791+
<h3>Conditional Textareas</h3>
792+
<p>Show different code sections based on binding values. Use <code>condition</code> attribute on <code>&lt;textarea&gt;</code> elements. Add <code>show-separators</code> to display visual separators between sections:</p>
793+
794+
<div class="code-header"><span>Written by developer</span></div>
795+
<interactive-code class="inline-code" language="html">
796+
<textarea><interactive-code language="typescript" show-separators>
797+
<textarea>const result = provider.complete(input, { groupBy: ${groupBy} });&lt;/textarea>
798+
<textarea condition="!groupBy">// Use result.items for flat list
799+
console.log(result.items);&lt;/textarea>
800+
<textarea condition="groupBy">// Use result.groups for grouped display
801+
console.log(result.groups);&lt;/textarea>
802+
<code-binding key="groupBy" type="select" options="undefined,'continent'" value="undefined"></code-binding>
803+
</interactive-code></textarea>
804+
</interactive-code>
805+
806+
<div class="preview-wrapper">
807+
<div class="preview-label">Seen by user (click on <code>undefined</code> to toggle)</div>
808+
<div class="user-view">
809+
<interactive-code language="typescript" show-separators>
810+
<textarea>const result = provider.complete(input, { groupBy: ${groupBy} });</textarea>
811+
<textarea condition="!groupBy">// Use result.items for flat list
812+
console.log(result.items);</textarea>
813+
<textarea condition="groupBy">// Use result.groups for grouped display
814+
console.log(result.groups);</textarea>
815+
<code-binding key="groupBy" type="select" options="undefined,'continent'" value="undefined"></code-binding>
816+
</interactive-code>
817+
</div>
818+
</div>
819+
</article>
788820
</section>
789821

790822
<!-- Documentation Section -->
@@ -840,6 +872,12 @@ <h3>API - &lt;interactive-code&gt;</h3>
840872
<td><code>'html'</code></td>
841873
<td>Syntax highlighting language</td>
842874
</tr>
875+
<tr>
876+
<td><code>show-separators</code></td>
877+
<td><code>boolean</code></td>
878+
<td><code>false</code></td>
879+
<td>Show visual separators between concatenated textarea sections</td>
880+
</tr>
843881
</tbody>
844882
</table>
845883
</div>
@@ -866,6 +904,11 @@ <h4>Properties</h4>
866904

867905
<h4>Content</h4>
868906
<p>Use <code>&lt;textarea&gt;</code> to define the code template (recommended). Alternative: <code>&lt;template&gt;</code>.</p>
907+
<p>Multiple <code>&lt;textarea&gt;</code> elements are concatenated. Use <code>condition</code> attribute for conditional display:</p>
908+
<ul class="feature-list">
909+
<li><code>condition="key"</code> - Show when binding value is truthy</li>
910+
<li><code>condition="!key"</code> - Show when binding value is falsy</li>
911+
</ul>
869912
</article>
870913

871914
<!-- API - CodeBindingElement -->

src/interactive-code.element.spec.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,4 +718,129 @@ describe('InteractiveCodeElement', () => {
718718
expect(input?.step).toBe('5');
719719
});
720720
});
721+
722+
describe('conditional textareas', () => {
723+
it('should show only unconditional textarea when no conditions match', async () => {
724+
element.innerHTML = `
725+
<textarea>always shown</textarea>
726+
<textarea condition="showExtra">extra content</textarea>
727+
<code-binding key="showExtra" type="boolean" value="false"></code-binding>
728+
`;
729+
document.body.appendChild(element);
730+
731+
await new Promise(resolve => setTimeout(resolve, 150));
732+
733+
const code = element.shadowRoot?.querySelector('code')?.textContent;
734+
expect(code).toContain('always shown');
735+
expect(code).not.toContain('extra content');
736+
});
737+
738+
it('should show conditional textarea when condition is true', async () => {
739+
element.innerHTML = `
740+
<textarea>always shown</textarea>
741+
<textarea condition="showExtra">extra content</textarea>
742+
<code-binding key="showExtra" type="boolean" value="true"></code-binding>
743+
`;
744+
document.body.appendChild(element);
745+
746+
await new Promise(resolve => setTimeout(resolve, 150));
747+
748+
const code = element.shadowRoot?.querySelector('code')?.textContent;
749+
expect(code).toContain('always shown');
750+
expect(code).toContain('extra content');
751+
});
752+
753+
it('should show negated conditional textarea when value is falsy', async () => {
754+
element.innerHTML = `
755+
<textarea condition="!grouped">flat list</textarea>
756+
<textarea condition="grouped">grouped list</textarea>
757+
<code-binding key="grouped" type="boolean" value="false"></code-binding>
758+
`;
759+
document.body.appendChild(element);
760+
761+
await new Promise(resolve => setTimeout(resolve, 150));
762+
763+
const code = element.shadowRoot?.querySelector('code')?.textContent;
764+
expect(code).toContain('flat list');
765+
expect(code).not.toContain('grouped list');
766+
});
767+
768+
it('should update displayed content when binding value changes', async () => {
769+
element.innerHTML = `
770+
<textarea condition="!grouped">flat list</textarea>
771+
<textarea condition="grouped">grouped list</textarea>
772+
<code-binding key="grouped" type="boolean" value="false"></code-binding>
773+
`;
774+
document.body.appendChild(element);
775+
776+
await new Promise(resolve => setTimeout(resolve, 150));
777+
778+
let code = element.shadowRoot?.querySelector('code')?.textContent;
779+
expect(code).toContain('flat list');
780+
expect(code).not.toContain('grouped list');
781+
782+
// Toggle the binding
783+
const binding = element.querySelector('code-binding') as any;
784+
binding.value = true;
785+
786+
await new Promise(resolve => setTimeout(resolve, 50));
787+
788+
code = element.shadowRoot?.querySelector('code')?.textContent;
789+
expect(code).not.toContain('flat list');
790+
expect(code).toContain('grouped list');
791+
});
792+
793+
it('should work with select type binding', async () => {
794+
element.innerHTML = `
795+
<textarea>const result = provider.complete(input, { groupBy: \${groupBy} });</textarea>
796+
<textarea condition="!groupBy">// Use result.items</textarea>
797+
<textarea condition="groupBy">// Use result.groups</textarea>
798+
<code-binding key="groupBy" type="select" options="undefined,'continent'" value="undefined"></code-binding>
799+
`;
800+
document.body.appendChild(element);
801+
802+
await new Promise(resolve => setTimeout(resolve, 150));
803+
804+
let code = element.shadowRoot?.querySelector('code')?.textContent;
805+
expect(code).toContain('result.items');
806+
expect(code).not.toContain('result.groups');
807+
808+
// Change to continent
809+
const binding = element.querySelector('code-binding') as any;
810+
binding.value = "'continent'";
811+
812+
await new Promise(resolve => setTimeout(resolve, 50));
813+
814+
code = element.shadowRoot?.querySelector('code')?.textContent;
815+
expect(code).not.toContain('result.items');
816+
expect(code).toContain('result.groups');
817+
});
818+
819+
it('should show separators between sections when show-separators attribute is set', async () => {
820+
element.setAttribute('show-separators', '');
821+
element.innerHTML = `
822+
<textarea>section 1</textarea>
823+
<textarea>section 2</textarea>
824+
`;
825+
document.body.appendChild(element);
826+
827+
await new Promise(resolve => setTimeout(resolve, 150));
828+
829+
const separator = element.shadowRoot?.querySelector('.section-separator');
830+
expect(separator).toBeTruthy();
831+
});
832+
833+
it('should not show separators when show-separators attribute is not set', async () => {
834+
element.innerHTML = `
835+
<textarea>section 1</textarea>
836+
<textarea>section 2</textarea>
837+
`;
838+
document.body.appendChild(element);
839+
840+
await new Promise(resolve => setTimeout(resolve, 150));
841+
842+
const separator = element.shadowRoot?.querySelector('.section-separator');
843+
expect(separator).toBeFalsy();
844+
});
845+
});
721846
});

0 commit comments

Comments
 (0)