Skip to content

Commit bf5cd4f

Browse files
nnelgxorzGlennBecker
andauthored
feat: Expose host:tagName on Components (#758)
* Add as attribute and better TagName type * πŸ˜ƒ * πŸ’‘ * Expose tagName on Host component and host:tagName * Fix host:tagName * Unit Tests * Tests and Docs * Remove duplicated type definitions * ❀️ * Remove last duplicated type * πŸ˜› * New implementation * Unit tests * Remove <Host tagName=""/> docs * Api update * Last bad doc reference Co-authored-by: GlennBecker <logosbyglenn@gmail.com>
1 parent 63ae99d commit bf5cd4f

File tree

6 files changed

+32
-13
lines changed

6 files changed

+32
-13
lines changed

β€Žpackages/docs/src/pages/docs/components/anatomy.mdxβ€Ž

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ Will result in:
100100

101101
Qwik host elements are marked with `q:host` attribute.
102102

103-
As you can see, `component$` will always create an extra element, by default is `div`, but that can be changed with `component$` options argument:
103+
As you can see, `component$` will always create an extra element, by default is `div`, but that can be changed with `component$` options argument or the `host:tagName` attribute:
104104

105105
```tsx
106106
const MyArticle = component$(() => (
@@ -110,7 +110,17 @@ const MyArticle = component$(() => (
110110
});
111111
```
112112

113-
Will result in:
113+
and:
114+
115+
```tsx
116+
const MyArticle = component$(() => (
117+
<span>My article</span>
118+
));
119+
120+
<MyArticle host:tagName="article"/>
121+
```
122+
123+
Will both result in:
114124

115125
```html
116126
<article q:host>
@@ -126,9 +136,6 @@ Since the host element is implicitly created by `component$`, it is not possible
126136

127137
Qwik uses host elements in various ways. For example when using `useHostElement()` function which retrieves it. It is also used to attach props to the components for serialization.
128138

129-
130-
131-
132139
## Host Element Attributes & Styling
133140

134141
Assume you have a component defined as so:
@@ -230,7 +237,6 @@ will result in:
230237
</div>
231238
```
232239

233-
234240
## Lazy Loading
235241

236242
The host component also serves an important role when breaking parent-child relationships for bundling purposes.

β€Žpackages/qwik/src/core/api.mdβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ export type Component<PROPS extends {}> = FunctionComponent<PublicProps<PROPS>>;
7070

7171
// @public
7272
export interface ComponentOptions {
73-
tagName?: string;
73+
// Warning: (ae-forgotten-export) The symbol "JSXTagName" needs to be exported by the entry point index.d.ts
74+
tagName?: JSXTagName;
7475
}
7576

7677
// @public

β€Žpackages/qwik/src/core/component/component.public.tsβ€Ž

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { $, QRL } from '../import/qrl.public';
22
import type { JSXNode } from '../render/jsx/types/jsx-node';
33
import { OnRenderProp } from '../util/markers';
4-
import type { ComponentBaseProps } from '../render/jsx/types/jsx-qwik-attributes';
4+
import type { ComponentBaseProps, JSXTagName } from '../render/jsx/types/jsx-qwik-attributes';
55
import type { FunctionComponent } from '../render/jsx/types/jsx-node';
66
import { jsx } from '../render/jsx/jsx-runtime';
77
import type { MutableWrapper } from '../object/q-object';
@@ -42,7 +42,7 @@ export interface ComponentOptions {
4242
* of host element requires that the parent component needs to know the tag name of the child
4343
* component synchronously.
4444
*/
45-
tagName?: string;
45+
tagName?: JSXTagName;
4646
}
4747

4848
/**
@@ -80,7 +80,7 @@ export type MutableProps<PROPS extends {}> = {
8080
*/
8181
export type EventHandler<T> = QRL<(value: T) => any>;
8282

83-
const ELEMENTS_SKIP_KEY = ['html', 'body', 'head'];
83+
const ELEMENTS_SKIP_KEY: JSXTagName[] = ['html', 'body', 'head'];
8484

8585
// <docs markdown="../readme.md#component">
8686
// !!DO NOT EDIT THIS COMMENT DIRECTLY!!!
@@ -145,8 +145,9 @@ export const componentQrl = <PROPS extends {}>(
145145

146146
// Return a QComponent Factory function.
147147
return function QSimpleComponent(props, key): JSXNode<PROPS> {
148+
const finalTag = props['host:tagName'] ?? tagName;
148149
const finalKey = skipKey ? undefined : onRenderQrl.getHash() + ':' + (key ? key : '');
149-
return jsx(tagName, { [OnRenderProp]: onRenderQrl, ...props }, finalKey) as any;
150+
return jsx(finalTag as string, { [OnRenderProp]: onRenderQrl, ...props }, finalKey) as any;
150151
};
151152
};
152153

β€Žpackages/qwik/src/core/render/jsx/host.public.tsβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { FunctionComponent } from './types/jsx-node';
44
export interface HostAttributes extends HTMLAttributes<HTMLElement> {
55
[key: string]: any;
66
}
7+
78
/**
89
* Place at the root of the component View to allow binding of attributes on the Host element.
910
*

β€Žpackages/qwik/src/core/render/jsx/types/jsx-qwik-attributes.tsβ€Ž

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ interface CSSProperties {
180180
[key: string]: string | number;
181181
}
182182

183+
/**
184+
* @public
185+
*/
186+
export type JSXTagName = keyof HTMLElementTagNameMap | Omit<string, keyof HTMLElementTagNameMap>;
187+
183188
/**
184189
* @public
185190
*/
@@ -207,6 +212,7 @@ export interface ComponentBaseProps {
207212
[key: `preventDefault:${string}`]: boolean;
208213
[key: `preventdefault:${string}`]: boolean;
209214

215+
'host:tagName'?: JSXTagName;
210216
children?: JSXChildren;
211217
}
212218
export interface QwikAttributes extends QwikProps, QwikEvents {}

β€Žpackages/qwik/src/core/render/render.unit.tsxβ€Ž

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ renderSuite('should serialize events correctly', async () => {
9090
></Div>
9191
);
9292
});
93-
9493
renderSuite('should serialize boolean attributes correctly', async () => {
9594
const fixture = new ElementFixture();
9695
await render(fixture.host, <input required={true} disabled={false}></input>);
@@ -107,7 +106,12 @@ renderSuite('should render into a document', async () => {
107106
);
108107
equal(fixture.document.body.innerHTML, 'WORKS');
109108
});
110-
109+
renderSuite('should accept and render host:tagName', async () => {
110+
const fixture = new ElementFixture();
111+
const tag: keyof HTMLElementTagNameMap = 'article';
112+
await render(fixture.host, <HelloWorld host:tagName={tag} />);
113+
equal(getFirstNode(fixture.host).tagName, tag.toUpperCase());
114+
});
111115
renderSuite('should render attributes', async () => {
112116
const fixture = new ElementFixture();
113117
await render(fixture.host, <div id="abc" title="bar" preventdefault:click></div>);

0 commit comments

Comments
Β (0)