Layout
A well-though, user-friendly layout is one of the most important aspect of a frontend application.
Whether we talk about classic top bar/sidebar navigation, peculiar layouts crafted around a specific need, or even no layout at all, micro-lc provides the flexibility to meet every requirement dynamically composing applications layout at runtime on the basis of a user-supplied configuration.
Mount point
To understand how layout works, it is important to talk about micro-lc mount point, the place in DOM on which micro-lc appends the dynamic content.
In its simplest (and default) form, micro-lc mount point is a <div>
tag with id __micro_lc
and a <style>
tag applying some base styling. The resulting tree depends on whether Shadow DOM is enabled, but regardless both the tags
are rendered in the default DOM.
- Tree with Shadow DOM
- Tree without Shadow DOM
<micro-lc>
#shadow-root (open)
<slot>
↪️ <style>
↪️ <div>
</slot>
<style>
div#__micro_lc {
width: 100%;
height: 100%;
}
div#__micro_lc > :first-child {
width: inherit;
height: inherit;
overflow: hidden
}
div#__micro_lc > :first-child > div.composer-body {
width: inherit;
height: inherit;
overflow: hidden
}
</style>
<div id="__micro_lc">
<!-- Here micro-lc will mount the dynamic content -->
</div>
</micro-lc>
<micro-lc disable-shadow-dom>
<style>
div#__micro_lc {
width: 100%;
height: 100%;
}
div#__micro_lc > :first-child {
width: inherit;
height: inherit;
overflow: hidden
}
div#__micro_lc > :first-child > div.composer-body {
width: inherit;
height: inherit;
overflow: hidden
}
</style>
<div id="__micro_lc">
<!-- Here micro-lc will mount the dynamic content -->
</div>
</micro-lc>
The <div>
is always rendered, since micro-lc needs it to ensure content is correctly mounted. However,
the <div>
id can be changed, and the preset style
can be disabled.
Any application or page mounted here is recommended to have its highest HTML element equipped with a css that allows its own internal scrolling. A good fit could be, for parcel applications,
.micro-lc-parcel-body {
height: inherit;
width: inherit;
overflow: auto;
}
Composition
micro-lc mount point can be personalized with the
configuration key mountPoint
, which accepts a
content definition.
type MountPoint = string | number | Component | (Component | number | string)[]
The rationale behind a mount point personalization depends on Shadow DOM status:
- if Shadow DOM is enabled, mount point allows to extend the layout with a portion outside Shadow DOM;
- if Shadow DOM is disabled, mount point is actually the only way to define a layout, since
layout
key is not considered.
In a customized mount point, the actual element in which micro-lc should append the content likely needs
to change. To instruct micro-lc of the new mount point, use the
configuration key mountPointSelector
, which accepts a
valid query selector to be run on
micro-lc base <div>
(i.e., the one with id __micro_lc
by default) subtree to find the new mount point.
Any content of the node referenced by mountPointSelector
will be substituted by micro-lc content.
If mountPointSelector
does not find a valid node, the whole mount point customization will be discarded and
micro-lc will revert to its default settings.
Example with Shadow DOM
- Configuration
- Resulting DOM
settings:
mountPoint:
tag: div
content:
- This is some addition to the layout outside micro-lc shadow-root
- tag: div
attributes:
id: custom_mount_point
mountPointSelector: "#custom_mount_point"
layout:
content:
tag: div
content:
- This is the layout
- tag: slot
<micro-lc>
#shadow-root (open)
<div>
This is the layout
<slot>
↪️ <style>
↪️ <div>
</slot>
</div>
<style>
div#__micro_lc {
width: 100%;
height: 100%;
}
div#__micro_lc > :first-child {
width: inherit;
height: inherit;
overflow: hidden
}
div#__micro_lc > :first-child > div.composer-body {
width: inherit;
height: inherit;
overflow: hidden
}
</style>
<div id="__micro_lc">
<div>
This is some addition to the layout outside micro-lc shadow-root
<div id="custom_mount_point">
<!-- Here micro-lc will mount the dynamic content -->
</div>
</div>
</div>
</micro-lc>
Example without Shadow DOM
- Configuration
- Resulting DOM
settings:
mountPoint:
tag: div
content:
- This is both layout and mount point, all outside micro-lc shadow-root
- tag: div
attributes:
id: custom_mount_point
mountPointSelector: "#custom_mount_point"
<micro-lc disable-shadow-dom>
<style>
div#__micro_lc {
width: 100%;
height: 100%;
}
div#__micro_lc > :first-child {
width: inherit;
height: inherit;
overflow: hidden
}
div#__micro_lc > :first-child > div.composer-body {
width: inherit;
height: inherit;
overflow: hidden
}
</style>
<div id="__micro_lc">
<div>
This is both layout and mount point, all outside micro-lc shadow-root
<div id="custom_mount_point">
<!-- Here micro-lc will mount the dynamic content -->
</div>
</div>
</div>
</micro-lc>
Build a layout
If micro-lc Shadow DOM is enabled, a
custom layout can be built using the configuration key layout
.
interface Layout {
sources?:
| string
| string[]
| {
uris: string | string[]
importmap?: ImportMap
}
content: Content
}
Through this structure, micro-lc is provided with a blueprint to follow to construct the desired DOM tree
dynamically at runtime. The building blocks (content
) are HTML5 elements or custom web components.
Take a look at micro-lc add-on components for ready-to-use layout solutions.
The layout interface (and the engine behind its creation) has the same shape of the interface used to build composable applications. Hence, refer to that section for a detailed description of the subject.
Slotting
When building custom layouts, slots can be used to mark placeholders to be filled with markup at runtime.
A micro-lc layout needs one unnamed <slot>
(i.e., a <slot>
without the
name attribute) to correctly mount the
dynamic content.
- Configuration
- Resulting DOM
layout:
content:
- tag: div
attributes:
class: top-bar
- tag: div
attributes:
class: main-content
content:
- tag: div
attributes:
class: side-bar
- tag: slot
<micro-lc>
#shadow-root (open)
<div class="top-bar"></div>
<div class="main-content">
<div class="side-bar"></div>
<slot>
↪️ <style>
↪️ <div>
</slot>
</div>
<style>
div#__micro_lc {
width: 100%;
height: 100%;
}
div#__micro_lc > :first-child {
width: inherit;
height: inherit;
overflow: hidden
}
div#__micro_lc > :first-child > div.composer-body {
width: inherit;
height: inherit;
overflow: hidden
}
</style>
<div id="__micro_lc">
<!-- Here micro-lc will mount the dynamic content -->
</div>
</micro-lc>
While micro-lc requires an unnamed <slot>
, you can use as many named slots as you want to allow external
injection of content in specific spots of the layout.
Two-level slotting
If you build a custom web component to be used as layout, and you want it to be in Shadow DOM too, you will face the issue of mounting the content through two levels of shadow-root.
To make this work, you need to append a <slot>
as sibling of the layout web component Shadow DOM with the same name of
the inner content <slot>
so that any sibling of the layout is correctly mounted.
- Custom web component
- Configuration
- Resulting DOM
class MyAwesomeWebComponent extends HTMLElement {
constructor() {
super()
this._shadowRoot = this.attachShadow({ mode: 'open' })
this._container = this.ownerDocument.createElement('div')
this._container.appendChild(this.ownerDocument.createElement('slot'))
this._shadowRoot.appendChild(this._container)
this.appendChild(this.ownerDocument.createElement('slot'))
}
}
customElements.define('my-awesome-component', MyAwesomeWebComponent)
layout:
sources: ./my-awesome-component.js
content:
- tag: my-awesome-component
<micro-lc>
#shadow-root (open)
<my-awesome-component>
#shadow-root (open)
<div>
<slot>
↪️ <slot>
</slot>
</div>
<slot>
↪️ <style>
↪️ <div>
</slot>
</my-awesome-component>
<style>
div#__micro_lc {
width: 100%;
height: 100%;
}
div#__micro_lc > :first-child {
width: inherit;
height: inherit;
overflow: hidden
}
div#__micro_lc > :first-child > div.composer-body {
width: inherit;
height: inherit;
overflow: hidden
}
</slot>
<div id="__micro_lc">
<!-- Here micro-lc will mount the dynamic content -->
</div>
</micro-lc>