├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── bun.lockb
├── bunfig.toml
├── dist
├── van-element.browser.js
├── van-element.js
└── van-element.umd.cjs
├── docs
├── .vitepress
│ ├── config.ts
│ └── theme
│ │ ├── custom.css
│ │ └── index.js
├── advanced
│ ├── shared-state.md
│ ├── slots.md
│ └── styling.md
├── components.ts
├── examples.md
├── index.md
├── intro
│ ├── get-started.md
│ ├── installation.md
│ └── tutorial.md
├── learn
│ ├── attributes.md
│ ├── lifecycle.md
│ ├── overview.md
│ └── shadow-options.md
└── public
│ ├── favicon.png
│ ├── logo.color.svg
│ ├── logo.dark.svg
│ └── logo.svg
├── index.html
├── package.json
├── scripts
└── build.ts
├── src
├── showcase.ts
├── van-element.d.ts
└── van-element.js
├── tests
├── internals.test.ts
├── light-dom.test.ts
└── setup.ts
├── tsconfig.json
├── types
└── van-element.d.ts
└── vite.config.ts
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | test:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: oven-sh/setup-bun@v2
15 | - run: bun install
16 | - run: bun test
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | docs/.vitepress/cache
3 | docs/.vitepress/dist
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib"
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Atmos4
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Van Element - WebComponents with VanJS
2 |
3 | A simple function to create VanJS web components. [See it in action](https://codepen.io/atmos4/pen/ZEPEvvB).
4 |
5 | ## Documentation
6 |
7 | https://van-element.pages.dev/.
8 |
9 | ## Usage
10 |
11 | ```javascript
12 | import van from "vanjs-core";
13 | import { define } from "vanjs-element";
14 |
15 | const { button, div, slot } = van.tags;
16 |
17 | define("custom-counter", () => {
18 | const counter = van.state(0);
19 | return div(
20 | slot(),
21 | counter,
22 | button({ onclick: () => ++counter.val }, "+"),
23 | button({ onclick: () => --counter.val }, "-")
24 | );
25 | });
26 | ```
27 |
28 | In your HTML:
29 |
30 | ```html
31 | ❤️
32 |
33 | 👌
34 | ```
35 |
36 | ## Why use this
37 |
38 | - automatic hydration of VanJS inside your HTML
39 | - reusable components without extra boilerplate
40 | - isolated styles and slots with Web components
41 | - extremely tiny (295B min+gzip)
42 |
--------------------------------------------------------------------------------
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Atmos4/van-element/c9c52cfddbb5820f7f70d6172b74610104b5908a/bun.lockb
--------------------------------------------------------------------------------
/bunfig.toml:
--------------------------------------------------------------------------------
1 | [test]
2 | preload = "./tests/setup.ts"
--------------------------------------------------------------------------------
/dist/van-element.browser.js:
--------------------------------------------------------------------------------
1 | window.vanE={define:function(t,e,s={mode:"open"}){window.customElements.define(t,class extends HTMLElement{constructor(){super(),this.a=[]}setAttribute(t,e){super.setAttribute(t,e),this.a[t]&&(this.a[t].val=e)}connectedCallback(){let t;van.add(s?this.attachShadow(s):this,e({attr:(t,e)=>this.a[t]??=van.state(this.getAttribute(t)??e),mount:e=>{let s=t;t=()=>{let t=s?.(),i=e();return()=>{t?.(),i?.()}}},$this:this})),this.d=t?.()}disconnectedCallback(){this.d?.()}})}};
--------------------------------------------------------------------------------
/dist/van-element.js:
--------------------------------------------------------------------------------
1 | import t from"vanjs-core";function e(e,s,i={mode:"open"}){window.customElements.define(e,class extends HTMLElement{constructor(){super(),this.a=[]}setAttribute(t,e){super.setAttribute(t,e),this.a[t]&&(this.a[t].val=e)}connectedCallback(){let e;t.add(i?this.attachShadow(i):this,s({attr:(e,s)=>this.a[e]??=t.state(this.getAttribute(e)??s),mount:t=>{let s=e;e=()=>{let e=s?.(),i=t();return()=>{e?.(),i?.()}}},$this:this})),this.d=e?.()}disconnectedCallback(){this.d?.()}})}export{e as define};
--------------------------------------------------------------------------------
/dist/van-element.umd.cjs:
--------------------------------------------------------------------------------
1 | (function(s,i){typeof exports=="object"&&typeof module<"u"?i(exports,require("vanjs-core")):typeof define=="function"&&define.amd?define(["exports","vanjs-core"],i):(s=typeof globalThis<"u"?globalThis:s||self,i(s.vanE={},s.van))})(this,function(s,i){"use strict";function c(f,h,a={mode:"open"}){window.customElements.define(f,class extends HTMLElement{constructor(){super(),this.a=[]}setAttribute(e,t){super.setAttribute(e,t),this.a[e]&&(this.a[e].val=t)}connectedCallback(){let e;i.add(a?this.attachShadow(a):this,h({attr:(t,n)=>{var d;return(d=this.a)[t]??(d[t]=i.state(this.getAttribute(t)??n))},mount:t=>{let n=e;e=()=>{let d=n==null?void 0:n(),o=t();return()=>{d==null||d(),o==null||o()}}},$this:this})),this.d=e==null?void 0:e()}disconnectedCallback(){var e;(e=this.d)==null||e.call(this)}})}s.define=c,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})});
2 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitepress";
2 |
3 | // https://vitepress.dev/reference/site-config
4 | export default defineConfig({
5 | title: "Van Element - Docs",
6 | description: "Documentation for Van Element",
7 | /* prettier-ignore */
8 | head: [
9 | ['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo.color.svg' }],
10 | ['link', { rel: 'icon', type: 'image/png', href: '/favicon.png' }],
11 | ['meta', { name: 'theme-color', content: '#fe3434' }],
12 | ['meta', { name: 'og:type', content: 'website' }],
13 | ['meta', { name: 'og:locale', content: 'en' }],
14 | ['meta', { name: 'og:site_name', content: 'Van Element' }],
15 | ],
16 | themeConfig: {
17 | logo: {
18 | src: "/logo.color.svg",
19 | },
20 | // https://vitepress.dev/reference/default-theme-config
21 | nav: [
22 | { text: "Get started", link: "/intro/get-started" },
23 | { text: "Tutorial", link: "/intro/tutorial" },
24 | { text: "Learn", link: "/learn/overview" },
25 | { text: "Examples", link: "/examples" },
26 | ],
27 |
28 | sidebar: [
29 | {
30 | text: "Introduction",
31 | base: "/intro/",
32 | items: [
33 | { text: "Get started", link: "get-started" },
34 | { text: "Installation", link: "installation" },
35 | { text: "Tutorial", link: "tutorial" },
36 | ],
37 | },
38 | {
39 | text: "Learn",
40 | base: "/learn/",
41 | items: [
42 | { text: "Overview", link: "overview" },
43 | { text: "Attributes", link: "attributes" },
44 | { text: "Lifecycle", link: "lifecycle" },
45 | { text: "Options", link: "shadow-options" },
46 | ],
47 | },
48 | {
49 | text: "Advanced",
50 | base: "/advanced/",
51 | items: [
52 | { text: "Slots", link: "slots" },
53 | { text: "Shared state", link: "shared-state" },
54 | { text: "Styling", link: "styling" },
55 | ],
56 | },
57 | { text: "Examples", link: "examples" },
58 | ],
59 |
60 | socialLinks: [
61 | { icon: "github", link: "https://github.com/Atmos4/van-element" },
62 | ],
63 | },
64 | vue: {
65 | template: {
66 | compilerOptions: {
67 | // All custom elements will be Van Elements
68 | isCustomElement: (tag) => tag.includes("-"),
69 | },
70 | },
71 | },
72 | });
73 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/custom.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --vp-c-brand-1: var(--vp-c-red-1);
3 | --vp-c-brand-2: var(--vp-c-red-2);
4 | --vp-c-brand-3: var(--vp-c-red-3);
5 |
6 | --vp-home-hero-name-color: transparent;
7 | --vp-home-hero-name-background: -webkit-linear-gradient(
8 | 120deg,
9 | #ffd341,
10 | #fe3434 100%
11 | );
12 | }
13 | fieldset {
14 | padding: 0 20px 10px;
15 | border-radius: 8px;
16 | border: 1px solid var(--vp-c-red-1);
17 | }
18 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.js:
--------------------------------------------------------------------------------
1 | import DefaultTheme from "vitepress/theme";
2 | import "./custom.css";
3 |
4 | export default {
5 | extends: DefaultTheme,
6 | async enhanceApp() {
7 | !import.meta.env.SSR && import("../../components.ts");
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/docs/advanced/shared-state.md:
--------------------------------------------------------------------------------
1 | # How to share state accross Van Elements
2 |
3 | There are a few techniques we can use to share state across different Van Elements
4 |
5 | ## Global state
6 |
7 | The simplest way to share state is to create a `van.state` object in the global scope, and share it between different Van Elements. You can even use [VanX.reactive](https://vanjs.org/x#reactive-object) to create a store.
8 |
9 | Here is a simple example:
10 |
11 | <<< @/components.ts#sharedState
12 |
13 | Now this state can be modified in one place:
14 |
15 | ```html
16 |
17 | ```
18 |
19 |
23 |
24 | And displayed in a different place in the DOM!
25 |
26 | ```html
27 |
28 | ```
29 |
30 |
34 |
35 | One downside of this approach is that the state is truly global. Try to click on another item in the sidebar then back here: the counter shoud still display the same value.
36 |
37 | Usually, it is not a big issue as global state is often suitable.
38 |
39 | ## Local state
40 |
41 | In order to achieve local shared state, we have to implement a _context_, similar to React Context. The context will serve state to its children (provider), which can then read its value (consumers).
42 |
43 | Fortunately, there is a context specification we can use to turn our Van Elements into context providers / consumers. Since it's a spec, it can even allow interaction with other contexts like Lit Context.
44 |
45 | ::: info Note
46 | I have not worked extensively on this solution since I don't think it is as useful. All I have is this [very raw CodePen](https://codepen.io/atmos4/pen/NWJNVNz) and some draft code on my computer somewhere.
47 |
48 | If this is of interest to you, feel free to create an issue and I will polish whatever code I have to turn it into a reusable package!
49 | :::
50 |
--------------------------------------------------------------------------------
/docs/advanced/slots.md:
--------------------------------------------------------------------------------
1 | # Slots
2 |
3 | ::: info
4 |
5 | You can only used slots if the Shadow DOM is enabled.
6 |
7 | :::
8 |
9 | Slots are like children in VanJS, but for Web Components.
10 |
11 | <<< @/components.ts#slots {javascript}
12 |
13 | ```html
14 | Robert and Marie
15 | ```
16 |
17 |
23 |
24 | Slots can have names, which allows you to customize many different places in the Van Element.
25 |
26 | <<< @/components.ts#slotsNames {javascript}
27 |
28 | ```html
29 |
30 | The title
31 |
The paragraph
32 |
33 | ```
34 |
35 |
42 |
43 | You will find more examples in the [Examples](../examples) section.
44 |
--------------------------------------------------------------------------------
/docs/advanced/styling.md:
--------------------------------------------------------------------------------
1 | # Styling
2 |
3 | There are several ways to style a Van Element.
4 |
5 | ::: warning
6 |
7 | The Shadow DOM gets in the way very often, and is even one of the main reasons Web Components face skepticism from the Web development community.
8 |
9 | If you don't want isolated styles, you can [use Van Elements without the Shadow DOM](../learn/shadow-options#disable-shadow-dom)!
10 |
11 | :::
12 |
13 | ## Inline styles
14 |
15 | The simplest way to style Van Elements is inline styles
16 |
17 | <<< @/components.ts#inlineStyles
18 |
19 | ```html
20 |
21 | ```
22 |
23 |
27 |
28 | Inline styles are often frowned upon for good reasons. However, in an isolated environment like the Shadow DOM, they can work really well if complex styling is not needed.
29 |
30 | ## `style` tag
31 |
32 | If you want more complex styling, using a `style` tag is a very good option. The reason it works is because the Shadow DOM will isolate these styles from the rest of the DOM so it won't leak out!
33 |
34 | ::: tip
35 |
36 | You can use the CSS selector `::slotted` to apply specific styles to the slotted element.
37 |
38 | :::
39 |
40 | <<< @/components.ts#styleTag
41 |
42 | ```html
43 |
49 |
50 | ```
51 |
52 | Now, thanks to the power of custom element reusability, we can reuse that confirmation modal anywhere, with custom text and actions.
53 |
54 |
55 |
56 | Tip of the day
57 |
Eat vegetables to stay healthy
58 |
59 |
60 | ```html
61 |
62 |
63 | Tip of the day
64 |
Eat vegetables to stay healthy
65 |
66 | ```
67 |
68 | ## 2. Normal VanJS code
69 |
70 | Van Element is just a way to hydrate VanJS. So we could simply take VanJS code and bind it to a custom element tag, and Van Element will put it in the DOM for us!
71 |
72 | As an example, let's shamefully take the Hello world program from VanJS's home page 🤫
73 |
74 | ::: details Code
75 | <<< @/components.ts#minigame {typescript}
76 | :::
77 |
78 | In order to hydrate this into the DOM, we just have to bind that VanJS function to a custom element tag:
79 |
80 | <<< @/components.ts#minigameBind {javascript}
81 |
82 | Now we can just slap that custom element anywhere in our HTML 🎉
83 |
84 | ```html
85 |
86 | ```
87 |
88 |
89 |
90 | As it is now, this component looks ugly because we did not style it. We have 2 solutions:
91 |
92 | - [Style it](./advanced/styling) within the Shadow DOM. This will make our component truly isolated and reusable.
93 | - [Disable the Shadow DOM](./learn/shadow-options#disable-shadow-dom), and style it with external stylesheets. Our component will depend on those stylesheets and isn't truly reusable anymore, but we can now use our favorite CSS framework to make it look beautiful!
94 |
95 | **Choose whichever option you prefer**. People like to get emotional over the Shadow DOM, but in most cases it's not needed.
96 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: Van Element
7 | tagline: Build reusable VanJS components easily.
8 | actions:
9 | - theme: brand
10 | text: Get Started
11 | link: /intro/get-started
12 | - theme: alt
13 | text: VanJS docs
14 | link: https://vanjs.org/
15 | image:
16 | src: /logo.color.svg
17 | alt: VitePress
18 |
19 | features:
20 | - icon: 🍦
21 | title: Build with VanJS
22 | details: Reactivity in 1kB.
23 | - icon: ⚙️
24 | title: The power of custom elements
25 | details: Reusable and framework-agnostic.
26 | - title: Lightweight and simple to use
27 | icon: 🌸
28 | details: One utility method, 300 bytes.
29 | ---
30 |
--------------------------------------------------------------------------------
/docs/intro/get-started.md:
--------------------------------------------------------------------------------
1 | # Hello and welcome 👋
2 |
3 | Here you can learn about Van Elements in different ways:
4 |
5 | - Read the following introduction for a brief summary.
6 | - Take a more hands-on approach with [the tutorial](./tutorial)
7 | - Browse [the examples](../examples) for concrete real-world applications
8 | - Dive in [the API overview](../learn/overview) if you like to read 🤓
9 |
10 | ## What is a Van Element
11 |
12 | A Van Element is a [VanJS](https://vanjs.org/) Web Component. You can create one with the `define` method:
13 |
14 | <<< @/components.ts#getstarted {javascript}
15 |
16 | Then this element can be used anywhere in HTML
17 |
18 | ```html
19 |
20 | ```
21 |
22 |
26 |
27 | ## Why Van Element
28 |
29 | [VanJS](https://vanjs.org/) is a fantastic ultra-lightweight option for building reactive UI. However, hydrating VanJS inside HTML can feel a bit awkward.
30 |
31 | A Van Element leverages native custom elements to automatically hydrate HTML with VanJS reactivity. It retains all the [benefits from VanJS](https://vanjs.org/#why-vanjs) with a few extra ones:
32 |
33 | - ### Reusability
34 |
35 | Once defined, Van Elements can be added, removed and reused anywhere in your HTML with a simple custom tag.
36 |
37 | - ### Portability
38 |
39 | Van Elements are standard Web Components that can work with any framework or templating language. You can use them in backend templating or inside frontend libraries like React, Vue or Svelte.
40 |
41 | - ### Isolation
42 |
43 | Thanks to the Shadow DOM, Van Elements benefit from style encapsulation and won't conflict with existing styles or other Web Components.
44 |
45 | - ### Control
46 |
47 | Van Elements can access the [custom element lifecycle](../learn/lifecycle) and manipulate Shadow DOM utilities like [slots](../advanced/slots) to make it easier to build interactive components.
48 |
49 | ## Web Components = 💩?
50 |
51 | > But why would I ever use Web Components? They are so hard to work with, I hate the Shadow DOM.
52 |
53 | The term `Web Components` is not a technical unity like React, but more of a concept regrouping two main APIs:
54 |
55 | - `custom elements`, the central part of Van Elements that enables hydration and lifecycle callbacks.
56 | - the `Shadow DOM`, a DOM and CSS isolation mechanism.
57 |
58 | Because the Shadow DOM isolates styles from the outside, it is very hard to work with when integrating with CSS frameworks, existing design systems or tools like Tailwind.
59 |
60 | Fortunately, **you can use Van Elements [without the Shadow DOM](../learn/shadow-options#disable-shadow-dom) and retain most of its benefits 🔥**
61 |
--------------------------------------------------------------------------------
/docs/intro/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | In order to use Van Elements, you will need:
4 |
5 | - Some understanding of Web Components ([Web Components MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Web_components).)
6 | - Basic knowledge of VanJS syntax ([VanJS docs](https://vanjs.org/))
7 |
8 | ### Package manager
9 |
10 | ::: code-group
11 |
12 | ```sh [npm]
13 | $ npm add vanjs-core vanjs-element
14 | ```
15 |
16 | ```sh [pnpm]
17 | $ pnpm add vanjs-core vanjs-element
18 | ```
19 |
20 | ```sh [yarn]
21 | $ yarn add vanjs-core vanjs-element
22 | ```
23 |
24 | ```sh [bun]
25 | $ bun add vanjs-core vanjs-element
26 | ```
27 |
28 | :::
29 |
30 | ```ts
31 | import van from "vanjs-core";
32 | import { define } from "vanjs-element";
33 | ```
34 |
35 | ### Browser
36 |
37 | Since Van Element doesn't require a build step, it can be loaded from a CDN or stored in a local file ([download on jsDelivr](https://www.jsdelivr.com/package/npm/vanjs-element)).
38 |
39 | ::: code-group
40 |
41 | ```html [CDN]
42 |
43 |
44 | ```
45 |
46 | ```html [Local files]
47 |
48 |
49 | ```
50 |
51 | ```html [Import maps]
52 |
53 |
61 |
67 | ```
68 |
69 | :::
70 |
71 | When imported in the global scope, you can use the global object `vanE`.
72 |
73 | ```javascript
74 | vanE.define(...);
75 | ```
76 |
77 | ::: warning Note:
78 |
79 | Since it uses `window.customElements`, Van Element only works in the browser and should not be used during SSR. Refer to the documentation of your framework to prevent it from defining Van Elements on the server.
80 |
81 | :::
82 |
--------------------------------------------------------------------------------
/docs/intro/tutorial.md:
--------------------------------------------------------------------------------
1 | # Tutorial
2 |
3 | Before starting, it is recommended that you take the [VanJS tutorial](https://vanjs.org/tutorial) 🙂
4 |
5 | You can follow the tutorial with [this CodePen template](https://codepen.io/pen?template=WNmQwLw), or just read along if you prefer!
6 |
7 | ## First element
8 |
9 | Let's build our first Van Element! It will just be a `span` with inline styles:
10 |
11 | ```js
12 | define("hello-world", () =>
13 | span({ style: "color:red;font-size:20px" }, "Hello world!")
14 | );
15 | ```
16 |
17 | ```html
18 | My first Van Element:
19 | ```
20 |
21 |
25 |
26 | ## Slots
27 |
28 | Let's add children to our Van element. We can use the `slot` for this.
29 |
30 | ```js
31 | const { span, slot } = van.tags;
32 |
33 | define("hello-world", () =>
34 | span({ style: "color:red;font-size:20px" }, slot())
35 | );
36 | ```
37 |
38 | ```html
39 | Cool discovery: the slot
40 | ```
41 |
42 |
46 |
47 | ::: tip
48 | Because they are Web components, Van Elements can use the `slot` tag as a way to inject children HTML elements. [Learn more about slots here!](../advanced/slots)
49 | :::
50 |
51 | ## Attributes
52 |
53 | It would be nice if we can change `color` and `font-size` from outside the Van Element, right?
54 |
55 | Meet the first property provided by Van Element: `attr()`. It takes an attribute name and an optional default value and returns a VanJS `State` object.
56 |
57 | ```js
58 | define("hello-world", ({ attr }) => {
59 | const color = attr(
60 | "color", // name of the attribute
61 | "red" // default value (optional)
62 | );
63 | const size = attr("size", 20);
64 | return span(
65 | { style: () => `color:${color.val};font-size:${size.val}` },
66 | slot()
67 | );
68 | });
69 | ```
70 |
71 | ```html
72 | I can be green
73 | or orange
74 | or red by default
75 | ```
76 |
77 |
83 |
84 | ## Isolated styles
85 |
86 | There is another way we can style our content instead of inline styles: by using a `style` tag.
87 |
88 | Our Van Element is isolated in the Shadow DOM, so whatever we write in that inner style won't leak out to the rest of the page!
89 |
90 | <<< @/components.ts#isolatedStyles {javascript}
91 |
92 | ```html
93 | The styles in the normal DOM
94 | or in other Van Elements
95 | won't be affected!
96 | ```
97 |
98 |
104 |
105 | ## Reactive Van Elements
106 |
107 | This tutorial is way too static. Let's add a bit of reactivity.
108 |
109 | Something nice about Van Elements is that you can reuse them... inside other Van Elements!
110 |
111 | As an example, let's build some handles for our Van Element:
112 |
113 | <<< @/components.ts#tuto4 {javascript}
114 |
115 | ```html
116 | Color sample
117 | ```
118 |
119 |
123 |
124 | ## Lifecycle
125 |
126 | Since `em` is not very visual, it would be nice to get the computed `font-size` in pixels. We could use `window.getComputedStyle` for this! Let's try it:
127 |
128 | <<< @/components.ts#tuto5
129 |
130 | ```html
131 | 1.5em
132 | 1.2em
133 | ```
134 |
135 |
140 |
141 | That doesn't seem to work 🤔 the reason is that slots only get populated _after_ the component has rendered.
142 |
143 | For this, there is the `mount` hook: it registers a function that only runs when the component has mounted:
144 |
145 | <<< @/components.ts#tuto5fixed
146 |
147 | ```html
148 | 1.5em
149 | 1.2em
150 | ```
151 |
152 |
157 |
158 | Now we get the proper font sizes!
159 |
160 | ## Self-reference
161 |
162 | There is one last thing we would want to do: we want to make sure our Van Element is used properly!
163 |
164 | Currently people can use anything in the slot: plain text, any HTML tags, even script tags 🤔 this might be intended for some components, but here we want to make sure that the only child of our Van Element is:
165 |
166 | - plain text
167 | - not white space
168 |
169 | We can access the reference of the Van Element using `$this`
170 |
171 | <<< @/components.ts#selfReference{2,3}
172 |
173 | ```html
174 | Correct usage
175 |
Wrong usage
176 | ```
177 |
178 |
183 |
184 | ## That's it!
185 |
186 | You have reached the end of the tutorial! Now you know basically everything there is to know about Van Elements. You can now freely explore the wonders of the Web Component world... or [disable the Shadow DOM](../learn/shadow-options#disable-shadow-dom) if you prefer!
187 |
--------------------------------------------------------------------------------
/docs/learn/attributes.md:
--------------------------------------------------------------------------------
1 | # Attributes
2 |
3 | You can retrieve attributes with the provided `attr` method. It takes an attribute name and an optional default value and returns a VanJS `State` object.
4 |
5 | Example:
6 |
7 | <<< @/components.ts#attributes {javascript}
8 |
9 | ```html
10 |
11 |
12 |
13 |
14 | ```
15 |
16 |
22 |
23 | ::: tip Note
24 |
25 | This method is a wrapper around `van.state`. Because of this, you will need to use [state derivation](https://vanjs.org/tutorial#state-derived-prop) in places you want to be reactive.
26 |
27 | ```js
28 | define("not-reactive", ({ attr }) => p(`Hello ${attr("name").val}`));
29 |
30 | define("very-reactive", ({ attr }) => p(() => `Hello ${attr("name").val}`));
31 | ```
32 |
33 | :::
34 |
35 | ## Attribute reactivity
36 |
37 | The `State` obtained from `attr()` is reactive to attribute change. This is useful when nesting Van Elements inside other Van Elements 🤯
38 |
39 | <<< @/components.ts#observed {javascript}
40 |
41 | ```html
42 |
43 | ```
44 |
45 |
49 |
50 | ::: tip Note
51 |
52 | You can use `kebab-case-attributes`.
53 |
54 | ```js
55 | const element = van.tags["some-element"]({ "data-text": "hello" });
56 | ```
57 |
58 | Resulting HTML:
59 |
60 | ```html
61 |
62 | ```
63 |
64 | However you cannot use `camelCaseAttributes` :pleading_face: it is not valid HTML syntax and will be turned into lowercase by the browser.
65 |
66 | :::
67 |
--------------------------------------------------------------------------------
/docs/learn/lifecycle.md:
--------------------------------------------------------------------------------
1 | # Lifecycle
2 |
3 | Sometimes, you want to execute code only when a Van Element has connected to the DOM. The most typical use case is when you try to access `assignedElements` from a slot:
4 |
5 | <<< @/components.ts#mountExample
6 |
7 | ```html
8 |
I am in the slot
9 | ```
10 |
11 |
15 |
16 | Here, the number of items in the slot is `0` :thinking: that is because slots will only get populated _after_ the Web Component has mounted.
17 |
18 | ## `mount`
19 |
20 | Fortunately, we can define a `mount` callback:
21 |
22 | <<< @/components.ts#mountShowcase
23 |
24 | ```html
25 |
I am in the slot
26 | ```
27 |
28 |
32 |
33 | ## `dismount`
34 |
35 | The `mount` function can return another callback that triggers when the component is dismounted.
36 |
37 | ```js
38 | mount(() => {
39 | console.log("mounted");
40 | return () => console.log("dismounted");
41 | });
42 | ```
43 |
44 | This can be useful for unsubscribing to certain events, keeping tracks of mounted elements, etc.
45 |
46 | ::: tip Note
47 |
48 | In most cases, you won't have to use `mount`. However, there are cases where you need it and it will then be very useful!
49 |
50 | :::
51 |
--------------------------------------------------------------------------------
/docs/learn/overview.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | `Van Element` exposes a single function: `define(...)`. It can take up to 3 arguments:
4 |
5 | - `name`
6 | Custom element tag.
7 | - `element`
8 | VanJS functional component.
9 | - `options` (_optional_)
10 | Extra [Shadow DOM options](./shadow-options).
11 |
12 | The provided VanJS functional component will be called with an object containing the following properties:
13 |
14 | - `attr()`
15 | Method to [retrieve the value of a given attribute](./attributes).
16 | - `mount()`
17 | Lifecycle hook to [register `mount` and `dismount` callbacks](./lifecycle).
18 | - `$this`
19 | Refers to the instance of the created custom element. Useful for accessing properties or binding event listeners.
20 |
--------------------------------------------------------------------------------
/docs/learn/shadow-options.md:
--------------------------------------------------------------------------------
1 | # Options
2 |
3 | Internally, Van Elements use `attachShadow` to attach a Shadow root to the element. You can change `attachShadow`'s options with an extra argument to the `define` function.
4 |
5 | ```js
6 | define("my-element", () => p("Closed root, delegating focus 🎉"), {
7 | mode: "closed",
8 | delegatesFocus: true,
9 | });
10 | ```
11 |
12 | You can read more about [the Shadow root options on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#parameters).
13 |
14 | ## Disable Shadow DOM
15 |
16 | Instead of the `options` object, you can pass `false` as third argument to disable the Shadow DOM completely.
17 |
18 | ```js
19 | define(
20 | "van-element",
21 | () => p("I don't like isolation 🤗"),
22 | false // Passing false as 3rd argument will disable the Shadow DOM
23 | );
24 | ```
25 |
26 | Things that will **stop working**:
27 |
28 | - DOM and style isolation
29 | - slots
30 |
31 | Everything else **will work the exact same**, including:
32 |
33 | - `$this`, `mount`, `attr`
34 | - all VanJS logic
35 | - hydration and reusability
36 |
37 | ## Shadow DOM or not?
38 |
39 | **You can safely disable the Shadow DOM if:**
40 |
41 | - All you want is easy hydration
42 | - Isolation gets in the way
43 | - You don't need slots
44 |
45 | **You _should not_ disable it if:**
46 |
47 | - You are building isolated components (component library, design system)
48 | - You need slots for composition
49 |
--------------------------------------------------------------------------------
/docs/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Atmos4/van-element/c9c52cfddbb5820f7f70d6172b74610104b5908a/docs/public/favicon.png
--------------------------------------------------------------------------------
/docs/public/logo.color.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/public/logo.dark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/public/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Van Element
7 |
8 |
9 |
33 |
40 |
41 |
42 |
Van Element showcase
43 |
44 |
Theme switch
45 |
46 |
Dynamic attribute injection
47 |
Here is
48 | a Van Element with attributes!
49 |
50 |
(Attributes can have
51 | default values)
52 |
53 |
It can be used inside other Van Elements:
54 | Reactive to attribute change! 🎉
55 |
56 |
Mount and dismount
57 |
58 |
59 |
Modal
60 |
61 |
62 |
Hello there!
63 |
I am a custom modal 🔥
64 |
To close me you can either:
65 |
66 |
click the ❌
67 |
click outside
68 |
press ESC
69 |
70 |
71 |
72 |
Without Shadow DOM
73 |
74 |
Tabs
75 |
76 |
How is it going? I am just a casual tab 🫡 Clicking other tabs will assign the tabs
77 | slot to another element!
78 |
79 |
The slotted content can be any HTML element or custom element, including Van Elements.
80 |
Let's reuse some elements as an example
81 |
82 |
83 |
I am another custom modal. Same component, but reused!
84 |
85 |
86 |
Here you go!
87 |
It's that easy to do. No duplicated code or hydration boilerplate. Just reusing the exact same component.
88 |