Skip to content

Commit 7b89169

Browse files
committed
feat: remove content property & add shadow mode detection to render property (#258)
BREAKING CHANGE: The `content` property is no longer supported. The `render` property must be used. In some cases, usage of the `shadow` option might be required.
1 parent 36d6e39 commit 7b89169

28 files changed

+571
-434
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ import Details from "./details.js";
104104
const Home = define({
105105
[router.connect]: { stack: [Details, ...] },
106106
tag: "app-home",
107-
content: () => html`
107+
render: () => html`
108108
<template layout="column">
109109
<h1>Home</h1>
110110
<nav layout="row gap">
@@ -118,7 +118,7 @@ const Home = define({
118118
export define({
119119
tag: "app-router",
120120
stack: router(Home),
121-
content: ({ stack }) => html`
121+
render: ({ stack }) => html`
122122
<template layout="column">
123123
${stack}
124124
</template>
@@ -139,7 +139,7 @@ Create CSS layouts in-place in templates, even without using Shadow DOM, but sti
139139
```javascript
140140
define({
141141
tag: "app-home-view",
142-
content: () => html`
142+
render: () => html`
143143
<template layout="column center gap:2">
144144
<div layout="grow grid:1|max">
145145
<h1>Home</h1>

docs/README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ import Details from "./details.js";
110110
const Home = define({
111111
[router.connect]: { stack: [Details, ...] },
112112
tag: "app-home",
113-
content: () => html`
113+
render: () => html`
114114
<template layout="column">
115115
<h1>Home</h1>
116116
<nav layout="row gap">
@@ -124,7 +124,7 @@ const Home = define({
124124
export define({
125125
tag: "app-router",
126126
stack: router(Home),
127-
content: ({ stack }) => html`
127+
render: ({ stack }) => html`
128128
<template layout="column">
129129
${stack}
130130
</template>
@@ -145,7 +145,7 @@ Create CSS layouts in-place in templates, even without using Shadow DOM, but sti
145145
```javascript
146146
define({
147147
tag: "app-home-view",
148-
content: () => html`
148+
render: () => html`
149149
<template layout="column center gap:2">
150150
<div layout="grow grid:1|max">
151151
<h1>Home</h1>

docs/component-model/definition.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ import Home from './views/home.js';
145145

146146
const App = {
147147
stack: router(Home),
148-
content: () => html`
148+
render: () => html`
149149
<template layout="column">
150150
...
151151
${stack}

docs/component-model/layout-engine.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ The feature supports both `content` and `render` properties of the component's d
2828
```js
2929
define({
3030
tag: "my-app-view",
31-
content: () => html`
31+
render: () => html`
3232
<template layout="column">
3333
...
3434
</template>

docs/component-model/structure.md

+61-65
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ The cache mechanism uses equality check to compare values (`nextValue` !== `last
1212

1313
## Reserved Keys
1414

15-
There are three reserved property names in the definition:
15+
There are two reserved property names in the definition:
1616

1717
* `tag` - a string which sets the custom element tag name
18-
* `render` and `content`, which expect the value as a function, and have additional options available
18+
* `render` - expects its value as a function for rendering the internal structure of the custom element
1919

2020
## Translation
2121

@@ -53,7 +53,7 @@ define({
5353
});
5454
```
5555

56-
Usually, the shorthand definition is more readable and less verbose, but the second one gives more control over the property behavior, as it provides additional options.
56+
Usually, the shorthand syntax is more readable and less verbose, but the second one gives more control over the property behavior, as it provides additional options.
5757

5858
## Attributes
5959

@@ -286,21 +286,31 @@ define({
286286
});
287287
```
288288
289-
## `render` & `content`
289+
## Rendering
290290
291-
The `render` and `content` properties are reserved for the rendering structure of the custom element. The `value` option must be a function, which returns a result of the call to the built-in template engine or a custom update function.
291+
The `render` property is reserved for the creating structure of the custom element. The `value` option must be a function, which returns a result of the call to the built-in template engine.
292292
293-
The library uses internally the `observe` pattern to called function automatically when dependencies change. As the property returns an update function, it can also be called manually, by `el.render()` or `el.content()`.
293+
The library uses the `observe` pattern to call the function automatically when dependencies change. As the property resolves to the update function, it can also be called manually, by `el.render()`.
294294
295-
> You can use built-in [template engine](/component/templates.md) with those properties without additional code
295+
### Element's Content
296+
297+
By default `render` property creates and updates the content of the custom element:
298+
299+
```javascript
300+
define({
301+
tag: "my-element",
302+
name: "",
303+
render: ({ name }) => html`<h1>Hello ${name}!</h1>`,
304+
});
305+
```
296306
297307
### Shadow DOM
298308
299-
Use the `render` key for the internal structure of the custom element, where you can add isolated styles, slot elements, etc.
309+
If the root template of the element includes styles or `<slot>` elements, the library renders the content to the shadow DOM:
300310
301-
```javascript
302-
import { define, html } from "hybrids";
311+
The template with inline styles:
303312
313+
```javascript
304314
define({
305315
tag: "my-element",
306316
name: "",
@@ -313,88 +323,74 @@ define({
313323
});
314324
```
315325
316-
The `render` property provides unique `options` key for passing additional arguments to `host.attachShadow()` method:
317-
318-
```ts
319-
render: {
320-
value: (host) => { ... },
321-
options: {
322-
mode: "open" | "closed",
323-
delegatesFocus: boolean,
324-
},
325-
...
326-
}
327-
```
326+
The template with `<slot>` element:
328327
329328
```javascript
330-
import { define, html } from "hybrids";
331-
332329
define({
333330
tag: "my-element",
334-
render: {
335-
value: html`<div>...</div>`,
336-
options: { delegatesFocus: true },
337-
},
331+
render: () => html`
332+
<div id="container">
333+
<slot></slot>
334+
</div>
335+
`,
338336
});
339337
```
340338
341-
### Element's Content
339+
!> Templates are compiled "just in time", so only the root template can be used to determine the rendering mode
342340
343-
Use the `content` property for rendering templates in the content of the custom element. By the design, it does not support isolated styles, slot elements, etc.
341+
### Explicit Mode
344342
345-
However, it is the way to build an app-like views structure, which can be rendered as a document content in light DOM. It is easily accessible in developer tools and search engines. For example, form elements (like `<input>`) have to be in the same subtree with the `<form>` element.
343+
If your nested template includes styles or slots, you must use the `shadow` option to force rendering in the Shadow DOM explicitly:
346344
347345
```javascript
348-
import { define, html } from "hybrids";
349-
350346
define({
351347
tag: "my-element",
352-
name: "",
353-
content: ({ name }) => html`<h1>Hello ${name}!</h1>`
348+
show: false,
349+
render: {
350+
value: ({ show }) => html`
351+
<div id="container">
352+
${show && html`<slot></slot>`}
353+
</div>
354+
`,
355+
shadow: true,
356+
},
354357
});
355358
```
356359
357-
### Custom Function
360+
You can use the `shadow` option to force both rendering modes:
358361
359-
The preferred way is to use a built-in [template engine](/component/templates.md), but you can use any function to update the DOM of the custom element, which accepts the following structure:
362+
```ts
363+
// Disable Shadow DOM (even if the template includes styles or slot elements)
364+
render: {
365+
value: (host) => html`...`.css`...`,
366+
shadow: false,
367+
...
368+
}
360369

361-
```javascript
362-
import React from "react";
363-
import ReactDOM from "react-dom";
364-
365-
export default function reactify(fn) {
366-
return (host) => {
367-
// get the component using the fn and host element
368-
const Component = fn(host);
369-
370-
// return the update function
371-
return (host, target) => {
372-
ReactDOM.render(Component, target);
373-
}
374-
}
370+
// Force Shadow DOM
371+
render: {
372+
value: (host) => html`...`,
373+
shadow: true,
375374
}
376375
```
377376
378-
```javascript
379-
import reactify from "./reactify.js";
377+
You can use `shadow` option for passing custom arguments to the `host.attachShadow()` method:
380378
381-
function MyComponent({ name }) {
382-
return <div>{name}</div>;
383-
}
379+
```javascript
380+
import { define, html } from "hybrids";
384381

385382
define({
386383
tag: "my-element",
387-
render: reactify(({ name }) => <MyComponent name={name} />),
388-
})
384+
render: {
385+
value: html`<div>...</div>`,
386+
shadow: { mode: "close", delegatesFocus: true },
387+
},
388+
});
389389
```
390390
391-
The above example uses the [`factory` pattern](#factories), to produce a function, which accepts the host element and returns the update function, which has `host` and `target` arguments. The `target` argument in the update function can be a `host` or `host.shadowRoot` depending on the property name.
392-
393-
!> The other properties from the `host` must be called in the main function body (not in the update function), as only then they will be correctly observed
394-
395391
### Reference Internals
396392
397-
Both `render` and `content` properties can be used to reference internals of the custom element. The DOM update process is asynchronous, so to avoid rendering timing issues, always use a property as a reference to the target element. If the property depending on `render` or `content` is called before the first update, the update will be triggered manually by calling the function.
393+
The `render` property can be used to reference internals of the custom element. The DOM update process is asynchronous, so to avoid rendering timing issues, always use the property as a reference to the target element. If the property depending on `render` is called before the first update, the update will be triggered manually by calling the function.
398394
399395
```javascript
400396
import { define, html } from "hybrids";
@@ -419,8 +415,8 @@ define({
419415
console.log("connected");
420416
return () => console.log("disconnected");
421417
},
422-
observe(host, value, lastValue) {
423-
console.log(`${value} -> ${lastValue}`);
418+
observe(host) {
419+
console.log("rendered");
424420
},
425421
},
426422
});

docs/component-model/templates.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ The built-in `html.transition` plugin utilizes the [Transition API](https://deve
545545
define({
546546
tag: 'my-app',
547547
stack: router([Home]),
548-
content: ({ stack }) => html`
548+
render: ({ stack }) => html`
549549
<header>...</header>
550550
<main>${stack}</main>
551551
...
@@ -560,7 +560,7 @@ The transition API can be customized by the CSS properties. The DOM elements mig
560560
```javascript
561561
export default define({
562562
tag: 'my-app-home',
563-
content: () => html`
563+
render: () => html`
564564
<template layout="column">
565565
...
566566
<div layout="view:card"></div>

docs/getting-started.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Otherwise, you can use it directly from a number of CDNs, which provide a hybrid
2121
define({
2222
tag: "hello-world",
2323
name: '',
24-
content: ({ name }) => html`<p>Hello ${name}!</p>`,
24+
render: ({ name }) => html`<p>Hello ${name}!</p>`,
2525
});
2626
</script>
2727
```

docs/migration.md

+38-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## v9.0.0
44

5-
The `v9.0` release brings simplification into the full object property descriptor and moves out some rarely used default behaviors into optional features.
5+
The `v9.0` release brings simplification into the full object property descriptor, removes the `content` property, and moves out some rarely used default behaviors into optional features.
66

77
### Descriptors
88

@@ -49,11 +49,11 @@ Writable properties are no longer automatically synchronized back to the attribu
4949

5050
Read more about the attribute synchronization in the [Structure](/component-model/structure.md#reflect) section.
5151

52-
### Render and Content
52+
### Render Property
5353

54-
#### Keys
54+
#### Key
5555

56-
The `render` and `content` properties are now reserved and expect an update function as a value (they cannot be used for other purpose). If you defined them as a full descriptor with custom behavior, you must rename them:
56+
The `render` property is now reserved and expects an update function as a value (it cannot be used for other purpose). If you defined it as a full descriptor with custom behavior, you must rename the property:
5757

5858
```javascript
5959
// before
@@ -74,7 +74,39 @@ The `render` and `content` properties are now reserved and expect an update func
7474
}
7575
```
7676

77-
#### Shadow DOM
77+
#### Content
78+
79+
From now, the `content` property has no special behavior, so it does not render. As the content should not include styles or `<slot>` elements, it is sufficient to just rename the property to `render`:
80+
81+
```javascript
82+
// before
83+
{
84+
content: () => html`...`,
85+
...
86+
}
87+
```
88+
89+
```javascript
90+
// after
91+
{
92+
render: () => html`...`,
93+
...
94+
}
95+
```
96+
97+
If you need to pass styles to the element's content, you can disable Shadow DOM explicitly:
98+
99+
```javascript
100+
{
101+
render: {
102+
value: () => html`...`.css`body { font-size: 14px }`,
103+
shadow: false,
104+
},
105+
...
106+
}
107+
```
108+
109+
#### Options
78110

79111
The options are now part of the `render` descriptor instead of a need to extend the `render` function:
80112

@@ -91,7 +123,7 @@ The options are now part of the `render` descriptor instead of a need to extend
91123
{
92124
render: {
93125
value: (host) => html`...`,
94-
options: { mode: "close" },
126+
shadow: { mode: "close" },
95127
},
96128
...
97129
}

docs/router/typescript.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface MyView {
1212
export default define<MyView>({
1313
[router.connect]: { url: "/my-view" },
1414
tag: "my-view",
15-
content: ({ param }) => html`
15+
render: ({ param }) => html`
1616
<p>${param}</p>
1717
`,
1818
});
@@ -60,7 +60,7 @@ interface MyView {
6060
const MyView: Component<MyView> = {
6161
[router.connect]: { url: "/my-view" },
6262
tag: "my-view",
63-
content: ({ param }) => html`
63+
render: ({ param }) => html`
6464
<p>${param}</p>
6565
`,
6666
};

0 commit comments

Comments
 (0)