Skip to content

Commit 728fbbf

Browse files
authored
Merge pull request #149 from CrowdStrike/toucan-form-expose-named-blocks
toucan-form: Expose named blocks for textarea
2 parents 8405d06 + 0ac0249 commit 728fbbf

File tree

3 files changed

+162
-17
lines changed

3 files changed

+162
-17
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,83 @@
1+
{{!
2+
Regarding Conditionals
3+
4+
This looks really messy, but Form::Fields::Textarea exposes named blocks; HOWEVER,
5+
we cannot conditionally render named blocks due to https://github.com/emberjs/rfcs/issues/735.
6+
7+
We *can* conditionally render components though, based on the blocks and argument combinations
8+
users provide us. This is very brittle, but until https://github.com/emberjs/rfcs/issues/735
9+
is resolved and a solution is found, this appears to be the only way to truly expose
10+
conditional named blocks.
11+
12+
---
13+
14+
Regarding glint-expect-error
15+
16+
"@onChange" of the textarea only expects a string typed value, but field.setValue is generic,
17+
accepting anything that DATA[KEY] could be. Similar case with "@value", but there casting to
18+
a string is easy.
19+
}}
120
<@form.Field @name={{@name}} as |field|>
2-
<Form::Fields::Textarea
3-
@label={{@label}}
4-
@hint={{@hint}}
5-
@error={{this.mapErrors field.rawErrors}}
6-
@value={{this.assertString field.value}}
7-
{{! The issue here is that onChange of textarea only expects a string typed value, but field.setValue is generic, accepting anything that DATA[KEY] could be. Similar case with @value, but there casting to a string is easy. }}
8-
{{! @glint-expect-error }}
9-
@onChange={{field.setValue}}
10-
@isDisabled={{@isDisabled}}
11-
@isReadOnly={{@isReadOnly}}
12-
@rootTestSelector={{@rootTestSelector}}
13-
name={{@name}}
14-
...attributes
15-
/>
21+
{{#if (this.hasOnlyLabelBlock (has-block 'label') (has-block 'hint'))}}
22+
<Form::Fields::Textarea
23+
@hint={{@hint}}
24+
@error={{this.mapErrors field.rawErrors}}
25+
@value={{this.assertString field.value}}
26+
{{! @glint-expect-error }}
27+
@onChange={{field.setValue}}
28+
@isDisabled={{@isDisabled}}
29+
@isReadOnly={{@isReadOnly}}
30+
@rootTestSelector={{@rootTestSelector}}
31+
name={{@name}}
32+
...attributes
33+
>
34+
<:label>{{yield to='label'}}</:label>
35+
</Form::Fields::Textarea>
36+
{{else if (this.hasHintAndLabelBlocks (has-block 'label') (has-block 'hint'))
37+
}}
38+
<Form::Fields::Textarea
39+
@error={{this.mapErrors field.rawErrors}}
40+
@value={{this.assertString field.value}}
41+
{{! @glint-expect-error }}
42+
@onChange={{field.setValue}}
43+
@isDisabled={{@isDisabled}}
44+
@isReadOnly={{@isReadOnly}}
45+
@rootTestSelector={{@rootTestSelector}}
46+
name={{@name}}
47+
...attributes
48+
>
49+
<:label>{{yield to='label'}}</:label>
50+
<:hint>{{yield to='hint'}}</:hint>
51+
</Form::Fields::Textarea>
52+
{{else if (this.hasLabelArgAndHintBlock @label (has-block 'hint'))}}
53+
<Form::Fields::Textarea
54+
@label={{@label}}
55+
@error={{this.mapErrors field.rawErrors}}
56+
@value={{this.assertString field.value}}
57+
{{! @glint-expect-error }}
58+
@onChange={{field.setValue}}
59+
@isDisabled={{@isDisabled}}
60+
@isReadOnly={{@isReadOnly}}
61+
@rootTestSelector={{@rootTestSelector}}
62+
name={{@name}}
63+
...attributes
64+
>
65+
<:hint>{{yield to='hint'}}</:hint>
66+
</Form::Fields::Textarea>
67+
{{else}}
68+
{{! Argument-only case }}
69+
<Form::Fields::Textarea
70+
@label={{@label}}
71+
@hint={{@hint}}
72+
@error={{this.mapErrors field.rawErrors}}
73+
@value={{this.assertString field.value}}
74+
{{! @glint-expect-error }}
75+
@onChange={{field.setValue}}
76+
@isDisabled={{@isDisabled}}
77+
@isReadOnly={{@isReadOnly}}
78+
@rootTestSelector={{@rootTestSelector}}
79+
name={{@name}}
80+
...attributes
81+
/>
82+
{{/if}}
1683
</@form.Field>

packages/ember-toucan-form/src/-private/textarea-field.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,20 @@ export interface ToucanFormTextareaFieldComponentSignature<
2525
*/
2626
form: HeadlessFormBlock<DATA>;
2727
};
28-
Blocks: {
29-
default: [];
30-
};
28+
Blocks: BaseTextareaFieldSignature['Blocks'];
3129
}
3230

3331
export default class ToucanFormTextareaFieldComponent<
3432
DATA extends UserData,
3533
KEY extends FormKey<FormData<DATA>> = FormKey<FormData<DATA>>
3634
> extends Component<ToucanFormTextareaFieldComponentSignature<DATA, KEY>> {
35+
hasOnlyLabelBlock = (hasLabel: boolean, hasHint: boolean) =>
36+
hasLabel && !hasHint;
37+
hasHintAndLabelBlocks = (hasLabel: boolean, hasHint: boolean) =>
38+
hasLabel && hasHint;
39+
hasLabelArgAndHintBlock = (hasLabel: string | undefined, hasHint: boolean) =>
40+
hasLabel && hasHint;
41+
3742
mapErrors = (errors?: ValidationError[]) => {
3843
if (!errors) {
3944
return;

test-app/tests/integration/components/toucan-form/form-textarea-test.gts

+73
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,77 @@ module('Integration | Component | ToucanForm | Textarea', function (hooks) {
3131

3232
assert.dom('[data-textarea]').hasAttribute('readonly');
3333
});
34+
35+
test('it renders `@label` and `@hint` component arguments', async function (assert) {
36+
const data: TestData = {
37+
text: 'multi-line text',
38+
};
39+
40+
await render(<template>
41+
<ToucanForm @data={{data}} as |form|>
42+
<form.Textarea @label="Label" @hint="Hint" @name="text" />
43+
</ToucanForm>
44+
</template>);
45+
46+
assert.dom('[data-label]').exists();
47+
assert.dom('[data-hint]').exists();
48+
});
49+
50+
test('it renders a `:label` named block with a `@hint` argument', async function (assert) {
51+
const data: TestData = {
52+
text: 'multi-line text',
53+
};
54+
55+
await render(<template>
56+
<ToucanForm @data={{data}} as |form|>
57+
<form.Textarea @name="text" @hint="Hint">
58+
<:label><span data-label-block>Label</span></:label>
59+
</form.Textarea>
60+
</ToucanForm>
61+
</template>);
62+
63+
assert.dom('[data-label-block]').exists();
64+
65+
// NOTE: `data-hint` comes from `@hint`.
66+
assert.dom('[data-hint]').exists();
67+
assert.dom('[data-hint]').hasText('Hint');
68+
});
69+
70+
test('it renders a `:hint` named block with a `@label` argument', async function (assert) {
71+
const data: TestData = {
72+
text: 'multi-line text',
73+
};
74+
75+
await render(<template>
76+
<ToucanForm @data={{data}} as |form|>
77+
<form.Textarea @label="Label" @name="text">
78+
<:hint><span data-hint-block>Hint</span></:hint>
79+
</form.Textarea>
80+
</ToucanForm>
81+
</template>);
82+
83+
// NOTE: `data-label` comes from `@label`.
84+
assert.dom('[data-label]').exists();
85+
assert.dom('[data-label]').hasText('Label');
86+
87+
assert.dom('[data-hint-block]').exists();
88+
});
89+
90+
test('it renders both a `:label` and `:hint` named block', async function (assert) {
91+
const data: TestData = {
92+
text: 'multi-line text',
93+
};
94+
95+
await render(<template>
96+
<ToucanForm @data={{data}} as |form|>
97+
<form.Textarea @label="Label" @name="text">
98+
<:label><span data-label-block>Label</span></:label>
99+
<:hint><span data-hint-block>Hint</span></:hint>
100+
</form.Textarea>
101+
</ToucanForm>
102+
</template>);
103+
104+
assert.dom('[data-label-block]').exists();
105+
assert.dom('[data-hint-block]').exists();
106+
});
34107
});

0 commit comments

Comments
 (0)