Skip to content

Commit 75c43e8

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 75c43e8

28 files changed

+539
-442
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

+49-72
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 (`css` and `style` helpers, or `<style>` elements) or `<slot>` element, the library will render the content to the shadow DOM:
300310
301-
```javascript
302-
import { define, html } from "hybrids";
311+
The template with styles:
303312
313+
```javascript
304314
define({
305315
tag: "my-element",
306316
name: "",
@@ -313,88 +323,55 @@ 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+
<slot></slot>
333+
`,
338334
});
339335
```
340336
341-
### Element's Content
337+
Templates are compiled "just in time", so only the root template can be used to determine the rendering mode. If your nested template includes styles or slots, you must use the `shadow` option to force rendering in the Shadow DOM explicitly.
342338
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.
339+
### Explicit Mode
344340
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.
341+
Use the `shadow` option of the `render` property to set rendering mode to Shadow DOM or element's content:
346342
347-
```javascript
348-
import { define, html } from "hybrids";
349-
350-
define({
351-
tag: "my-element",
352-
name: "",
353-
content: ({ name }) => html`<h1>Hello ${name}!</h1>`
354-
});
355-
```
356-
357-
### Custom Function
358-
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:
343+
```ts
344+
// Disable Shadow DOM
345+
render: {
346+
value: (host) => html`...`,
347+
shadow: false,
348+
...
349+
}
360350

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-
}
351+
// Force Shadow DOM
352+
render: {
353+
value: (host) => html`...`,
354+
shadow: true,
375355
}
376356
```
377357
378-
```javascript
379-
import reactify from "./reactify.js";
358+
You can use this option for passing custom values to the `host.attachShadow()` method:
380359
381-
function MyComponent({ name }) {
382-
return <div>{name}</div>;
383-
}
360+
```javascript
361+
import { define, html } from "hybrids";
384362

385363
define({
386364
tag: "my-element",
387-
render: reactify(({ name }) => <MyComponent name={name} />),
388-
})
365+
render: {
366+
value: html`<div>...</div>`,
367+
shadow: { mode: "close", delegatesFocus: true },
368+
},
369+
});
389370
```
390371
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-
395372
### Reference Internals
396373
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.
374+
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.
398375
399376
```javascript
400377
import { define, html } from "hybrids";
@@ -419,8 +396,8 @@ define({
419396
console.log("connected");
420397
return () => console.log("disconnected");
421398
},
422-
observe(host, value, lastValue) {
423-
console.log(`${value} -> ${lastValue}`);
399+
observe(host) {
400+
console.log("rendered");
424401
},
425402
},
426403
});

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)