Skip to content

Commit 2b30faa

Browse files
committed
feat: remove content property & add shadow mode detection to render property
BREAKING CHANGE: `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 2b0c01b commit 2b30faa

20 files changed

+552
-466
lines changed

docs/component-model/structure.md

+38-70
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,17 +286,29 @@ define({
286286
});
287287
```
288288
289-
## `render` & `content`
289+
## `render`
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 a 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 internally 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()` or `el.content()`.
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 updates the content of the custom element:
298+
299+
```javascript
300+
import { define, html } from "hybrids";
301+
302+
define({
303+
tag: "my-element",
304+
name: "",
305+
render: ({ name }) => html`<h1>Hello ${name}!</h1>`,
306+
});
307+
```
296308
297309
### Shadow DOM
298310
299-
Use the `render` key for the internal structure of the custom element, where you can add isolated styles, slot elements, etc.
311+
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:
300312
301313
```javascript
302314
import { define, html } from "hybrids";
@@ -313,88 +325,44 @@ define({
313325
});
314326
```
315327
316-
The `render` property provides unique `options` key for passing additional arguments to `host.attachShadow()` method:
328+
!> Templates are compiled lazy, 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
329+
330+
### Explicit Mode
331+
332+
Use unique `shadow` option of the `render` property to force rendering in Shadow DOM or element's content:
317333
318334
```ts
335+
// Disable Shadow DOM
319336
render: {
320-
value: (host) => { ... },
321-
options: {
322-
mode: "open" | "closed",
323-
delegatesFocus: boolean,
324-
},
337+
value: (host) => html`...`,
338+
shadow: false,
325339
...
326340
}
341+
342+
// Enable Shadow DOM
343+
render: {
344+
value: (host) => html`...`,
345+
shadow: true,
346+
}
327347
```
328348
349+
You can use `shadow` option for passing custom argument to the `host.attachShadow()` method:
350+
329351
```javascript
330352
import { define, html } from "hybrids";
331353

332354
define({
333355
tag: "my-element",
334356
render: {
335357
value: html`<div>...</div>`,
336-
options: { delegatesFocus: true },
358+
options: { mode: "close", delegatesFocus: true },
337359
},
338360
});
339361
```
340362
341-
### Element's Content
342-
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.
344-
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.
346-
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:
360-
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-
}
375-
}
376-
```
377-
378-
```javascript
379-
import reactify from "./reactify.js";
380-
381-
function MyComponent({ name }) {
382-
return <div>{name}</div>;
383-
}
384-
385-
define({
386-
tag: "my-element",
387-
render: reactify(({ name }) => <MyComponent name={name} />),
388-
})
389-
```
390-
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-
395363
### Reference Internals
396364
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.
365+
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 a 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.
398366
399367
```javascript
400368
import { define, html } from "hybrids";

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
}

src/define.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,7 @@ function compile(hybrids, HybridsElement) {
8585
);
8686
}
8787

88-
desc =
89-
key === "render" || key === "content"
90-
? render(key, desc)
91-
: value(key, desc);
88+
desc = key === "render" ? render(desc) : value(key, desc);
9289

9390
if (desc.writable) {
9491
writable.add(key);

src/localize.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { getPlaceholder } from "./template/utils.js";
22

3-
import { probablyDevMode } from "./utils.js";
43
import { compile } from "./template/index.js";
54

65
const dictionary = new Map();
@@ -81,7 +80,7 @@ export function get(key, context, args = []) {
8180
}
8281
if (!msg) {
8382
msg = key;
84-
if ((dictionary.size || translate) && probablyDevMode) {
83+
if (dictionary.size || translate) {
8584
console.warn(
8685
`Missing translation: "${key}"${context ? ` [${context}]` : ""}`,
8786
);

src/render.js

+10-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
export default function render(key, desc) {
1+
export default function render(desc) {
22
if (desc.reflect) {
3-
throw TypeError(`'reflect' option is not supported for '${key}' property`);
3+
throw TypeError(`'reflect' option is not supported for 'render' property`);
44
}
55

66
const { value: fn, observe } = desc;
77

88
if (typeof fn !== "function") {
99
throw TypeError(
10-
`Value for '${key}' property must be a function: ${typeof fn}`,
10+
`Value for 'render' property must be a function: ${typeof fn}`,
1111
);
1212
}
1313

@@ -22,19 +22,17 @@ export default function render(key, desc) {
2222
},
2323
};
2424

25-
let shadowOptions = undefined;
26-
if (key === "render") {
27-
const options = desc.options || {};
28-
shadowOptions = {
29-
mode: options.mode || "open",
30-
delegatesFocus: options.delegatesFocus || false,
31-
};
32-
}
25+
const shadowOptions = desc.shadow
26+
? {
27+
mode: desc.shadow.mode || "open",
28+
delegatesFocus: desc.shadow.delegatesFocus || false,
29+
}
30+
: desc.shadow;
3331

3432
return {
3533
value: (host) => {
3634
const updateDOM = fn(host);
37-
return () => updateDOM(host, null, shadowOptions);
35+
return () => updateDOM(host, shadowOptions);
3836
},
3937
...rest,
4038
};

0 commit comments

Comments
 (0)