Skip to content

Commit eaaea9a

Browse files
authored
Improve @contentHtmlTag typing, add tests and update docs (#1039)
1 parent ce03600 commit eaaea9a

File tree

7 files changed

+133
-25
lines changed

7 files changed

+133
-25
lines changed

docs/app/templates/public-pages/docs/api-reference.gts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ import { LinkTo } from '@ember/routing';
131131
<em>probably</em>
132132
don't want to use this option.</td>
133133
</tr>
134+
<tr>
135+
<td>contentHtmlTag</td>
136+
<td><code>String</code></td>
137+
<td>(Default: <code>'div'</code>) The tag of the content component</td>
138+
</tr>
134139
<tr>
135140
<td>registerAPI</td>
136141
<td><code>Function</code></td>
@@ -265,11 +270,6 @@ import { LinkTo } from '@ember/routing';
265270
<td>Flag to determine whether the content will allow CSS animations.
266271
Defaults to true</td>
267272
</tr>
268-
<tr>
269-
<td>htmlTag</td>
270-
<td>String</td>
271-
<td>(Default: <code>'div'</code>) The tag of the content component</td>
272-
</tr>
273273
<tr>
274274
<td>shouldReposition</td>
275275
<td>Function</td>

docs/app/templates/public-pages/docs/migrate-8-0-to-9-0.gts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ import { LinkTo } from '@ember/routing';
6060
a regular HTML attribute instead.
6161
</p>
6262
</li>
63+
<li>
64+
<p>
65+
If you are using
66+
<code>@htmlTag</code>
67+
on the yielded content component (for example,
68+
<code>&lt;dd.Content @htmlTag="span"&gt;</code>) replace it with
69+
<code>&lt;BasicDropdown @contentHtmlTag="span"&gt;</code>
70+
This change is required to fix Glint v2 typing issues.
71+
<br />
72+
<small><i>(Deprecation added in 8.11)</i></small>
73+
</p>
74+
</li>
6375
<li>
6476
<p>
6577
If you are using

ember-basic-dropdown/src/components/basic-dropdown-content.gts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import hasMoved from '../utils/has-moved.ts';
1111
import { isTesting } from '@embroider/macros';
1212
import { modifier } from 'ember-modifier';
13-
import { element } from 'ember-element-helper';
13+
import { element, type ElementFromTagName } from 'ember-element-helper';
1414
import { or } from 'ember-truth-helpers';
1515
import style from 'ember-style-modifier';
1616
import { on } from '@ember/modifier';
@@ -23,8 +23,10 @@ import type {
2323
VerticalPosition,
2424
} from '../types.ts';
2525

26-
export interface BasicDropdownContentSignature {
27-
Element: HTMLElement;
26+
export interface BasicDropdownContentSignature<
27+
T extends keyof HTMLElementTagNameMap = 'div',
28+
> {
29+
Element: ElementFromTagName<T>;
2830
Args: {
2931
animationEnabled?: boolean;
3032
transitioningInClass?: string;
@@ -47,7 +49,7 @@ export interface BasicDropdownContentSignature {
4749
vPosition?: VerticalPosition | null;
4850
defaultClass?: string;
4951
overlay?: boolean;
50-
htmlTag?: keyof HTMLElementTagNameMap | undefined;
52+
htmlTag?: T | undefined;
5153
onFocusIn?: (dropdown?: Dropdown, event?: FocusEvent) => void;
5254
onFocusOut?: (dropdown?: Dropdown, event?: FocusEvent) => void;
5355
onMouseEnter?: (dropdown?: Dropdown, event?: MouseEvent) => void;
@@ -64,7 +66,9 @@ export interface BasicDropdownContentSignature {
6466

6567
type RootMouseDownHandler = (ev: MouseEvent | TouchEvent) => void;
6668

67-
export default class BasicDropdownContent extends Component<BasicDropdownContentSignature> {
69+
export default class BasicDropdownContent<
70+
T extends keyof HTMLElementTagNameMap,
71+
> extends Component<BasicDropdownContentSignature<T>> {
6872
transitioningInClass =
6973
this.args.transitioningInClass || 'ember-basic-dropdown--transitioning-in';
7074
transitionedInClass =
@@ -133,6 +137,10 @@ export default class BasicDropdownContent extends Component<BasicDropdownContent
133137
return style;
134138
}
135139

140+
get tag(): keyof HTMLElementTagNameMap {
141+
return this.args.htmlTag || 'div';
142+
}
143+
136144
/**
137145
* Allows similair behaviour to `ember-composable-helpers`' `optional` helper.
138146
* Avoids adding extra dependencies.
@@ -543,7 +551,7 @@ export default class BasicDropdownContent extends Component<BasicDropdownContent
543551
<div class="ember-basic-dropdown-overlay"></div>
544552
{{/if}}
545553

546-
{{#let (element (or @htmlTag "div")) as |OptionalTag|}}
554+
{{#let (element this.tag) as |OptionalTag|}}
547555
<OptionalTag
548556
id={{this.dropdownId}}
549557
class="ember-basic-dropdown-content ember-basic-dropdown-content--{{@hPosition}}
@@ -574,7 +582,7 @@ export default class BasicDropdownContent extends Component<BasicDropdownContent
574582
<div class="ember-basic-dropdown-overlay"></div>
575583
{{/if}}
576584

577-
{{#let (element (or @htmlTag "div")) as |OptionalTag|}}
585+
{{#let (element this.tag) as |OptionalTag|}}
578586
<OptionalTag
579587
id={{this.dropdownId}}
580588
class="ember-basic-dropdown-content ember-basic-dropdown-content--{{@hPosition}}

ember-basic-dropdown/src/components/basic-dropdown-trigger.gts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ export interface BasicDropdownTriggerSignature<
4141
export default class BasicDropdownTrigger<
4242
T extends keyof HTMLElementTagNameMap,
4343
> extends Component<BasicDropdownTriggerSignature<T>> {
44+
get tag(): keyof HTMLElementTagNameMap {
45+
return this.args.htmlTag || 'div';
46+
}
47+
4448
// Actions
4549
/**
4650
* Allows similar behavior to `ember-composable-helpers`' `optional` helper.
@@ -63,10 +67,6 @@ export default class BasicDropdownTrigger<
6367
}
6468
}
6569

66-
get tag(): keyof HTMLElementTagNameMap {
67-
return this.args.htmlTag || 'div';
68-
}
69-
7070
<template>
7171
{{#if @dropdown}}
7272
{{#let (element this.tag) as |OptionalTag|}}

ember-basic-dropdown/src/components/basic-dropdown.gts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const IGNORED_STYLES = ['top', 'left', 'right', 'width', 'height'];
3737

3838
export interface BasicDropdownDefaultBlock<
3939
TriggerHtmlTag extends keyof HTMLElementTagNameMap = 'div',
40+
ContentHtmlTag extends keyof HTMLElementTagNameMap = 'div',
4041
> {
4142
uniqueId: string;
4243
disabled: boolean;
@@ -50,21 +51,30 @@ export interface BasicDropdownDefaultBlock<
5051
>;
5152
}
5253
>;
53-
Content: ComponentLike<BasicDropdownContentSignature>;
54+
Content: ComponentLike<
55+
Omit<BasicDropdownContentSignature<ContentHtmlTag>, 'Args'> & {
56+
Args: Omit<
57+
BasicDropdownContentSignature<ContentHtmlTag>['Args'],
58+
'htmlTag'
59+
>;
60+
}
61+
>;
5462
}
5563

5664
export interface BasicDropdownSignature<
5765
TriggerHtmlTag extends keyof HTMLElementTagNameMap = 'div',
66+
ContentHtmlTag extends keyof HTMLElementTagNameMap = 'div',
5867
> {
5968
Element: HTMLElement;
60-
Args: BasicDropdownArgs<TriggerHtmlTag>;
69+
Args: BasicDropdownArgs<TriggerHtmlTag, ContentHtmlTag>;
6170
Blocks: {
62-
default: [BasicDropdownDefaultBlock<TriggerHtmlTag>];
71+
default: [BasicDropdownDefaultBlock<TriggerHtmlTag, ContentHtmlTag>];
6372
};
6473
}
6574

6675
export interface BasicDropdownArgs<
6776
TriggerHtmlTag extends keyof HTMLElementTagNameMap = 'div',
77+
ContentHtmlTag extends keyof HTMLElementTagNameMap = 'div',
6878
> {
6979
initiallyOpened?: boolean;
7080
renderInPlace?: boolean;
@@ -78,21 +88,24 @@ export interface BasicDropdownArgs<
7888
preventScroll?: boolean;
7989
matchTriggerWidth?: boolean;
8090
triggerHtmlTag?: TriggerHtmlTag;
81-
contentHtmlTag?: keyof HTMLElementTagNameMap;
91+
contentHtmlTag?: ContentHtmlTag;
8292
onInit?: (dropdown: Dropdown) => void;
8393
registerAPI?: (dropdown: Dropdown | null) => void;
8494
onOpen?: (dropdown: Dropdown, e?: Event) => boolean | void;
8595
onClose?: (dropdown: Dropdown, e?: Event) => boolean | void;
8696
triggerComponent?:
8797
| ComponentLike<BasicDropdownTriggerSignature<TriggerHtmlTag>>
8898
| undefined;
89-
contentComponent?: ComponentLike<BasicDropdownContentSignature> | undefined;
99+
contentComponent?:
100+
| ComponentLike<BasicDropdownContentSignature<ContentHtmlTag>>
101+
| undefined;
90102
calculatePosition?: CalculatePosition;
91103
}
92104

93105
export default class BasicDropdown<
94106
TriggerHtmlTag extends keyof HTMLElementTagNameMap = 'div',
95-
> extends Component<BasicDropdownSignature<TriggerHtmlTag>> {
107+
ContentHtmlTag extends keyof HTMLElementTagNameMap = 'div',
108+
> extends Component<BasicDropdownSignature<TriggerHtmlTag, ContentHtmlTag>> {
96109
@tracked hPosition: HorizontalPosition | null = null;
97110
@tracked vPosition: VerticalPosition | null = null;
98111
@tracked top: string | undefined;
@@ -190,7 +203,10 @@ export default class BasicDropdown<
190203
}
191204

192205
// Lifecycle hooks
193-
constructor(owner: Owner, args: BasicDropdownArgs<TriggerHtmlTag>) {
206+
constructor(
207+
owner: Owner,
208+
args: BasicDropdownArgs<TriggerHtmlTag, ContentHtmlTag>,
209+
) {
194210
super(owner, args);
195211
if (this.args.onInit) {
196212
this.args.onInit(this.publicAPI);
@@ -520,10 +536,14 @@ export default class BasicDropdown<
520536
);
521537
}
522538

523-
get contentComponent(): ComponentLike<BasicDropdownContentSignature> {
539+
get contentComponent(): ComponentLike<
540+
BasicDropdownContentSignature<ContentHtmlTag>
541+
> {
524542
return (
525543
this.args.contentComponent ||
526-
(BasicDropdownContent as ComponentLike<BasicDropdownContentSignature>)
544+
(BasicDropdownContent as ComponentLike<
545+
BasicDropdownContentSignature<ContentHtmlTag>
546+
>)
527547
);
528548
}
529549

test-app/tests/integration/components/basic-dropdown-test.gts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1731,6 +1731,48 @@ module('Integration | Component | basic-dropdown', function (hooks) {
17311731
);
17321732
});
17331733

1734+
test<ExtendedTestContext>('Passing @triggerHtmlTag and @contentHtmlTag works', async function (assert) {
1735+
assert.expect(2);
1736+
1737+
await render(
1738+
<template>
1739+
<BasicDropdown
1740+
@renderInPlace={{true}}
1741+
@triggerHtmlTag="button"
1742+
@contentHtmlTag="span"
1743+
as |dropdown|
1744+
>
1745+
<dropdown.Trigger>Press me</dropdown.Trigger>
1746+
<dropdown.Content><h3>Content of the dropdown</h3></dropdown.Content>
1747+
</BasicDropdown>
1748+
</template>,
1749+
);
1750+
1751+
assert.strictEqual(
1752+
(
1753+
getRootNode(this.element).querySelector(
1754+
'.ember-basic-dropdown-trigger',
1755+
) as HTMLElement
1756+
).tagName,
1757+
'BUTTON',
1758+
);
1759+
1760+
await click(
1761+
getRootNode(this.element).querySelector(
1762+
'.ember-basic-dropdown-trigger',
1763+
) as HTMLElement,
1764+
);
1765+
1766+
assert.strictEqual(
1767+
(
1768+
getRootNode(this.element).querySelector(
1769+
'.ember-basic-dropdown-content',
1770+
) as HTMLElement
1771+
).tagName,
1772+
'SPAN',
1773+
);
1774+
});
1775+
17341776
// Shadow dom test
17351777
test<ExtendedTestContext>('Shadow dom: Its `toggle` action opens and closes the dropdown', async function (assert) {
17361778
const wormhole = document.createElement('div');

test-app/tests/integration/components/content-test.gts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,5 +1411,31 @@ module('Integration | Component | basic-dropdown-content', function (hooks) {
14111411
'mouseleave',
14121412
);
14131413
});
1414+
1415+
test<ExtendedTestContext>('If it receives `@htmlTag`, the content uses that tag name', async function (assert) {
1416+
const self = this;
1417+
1418+
await render<ExtendedTestContext>(
1419+
<template>
1420+
<div id="destination-el"></div>
1421+
<BasicDropdownContent
1422+
@dropdown={{self.dropdown}}
1423+
@destination="destination-el"
1424+
@htmlTag="span"
1425+
>
1426+
Content
1427+
</BasicDropdownContent>
1428+
</template>,
1429+
);
1430+
1431+
assert.strictEqual(
1432+
(
1433+
getRootNode(this.element).querySelector(
1434+
'.ember-basic-dropdown-content',
1435+
) as HTMLElement
1436+
).tagName,
1437+
'SPAN',
1438+
);
1439+
});
14141440
});
14151441
});

0 commit comments

Comments
 (0)