chore: format code

This commit is contained in:
Ayo Ayco 2024-12-19 22:43:12 +01:00
parent ae2df5096d
commit 833e835f65
51 changed files with 3547 additions and 1684 deletions

View file

@ -2,12 +2,12 @@ name: ESLint
on: on:
push: push:
branches: ["main"] branches: ['main']
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: ["main"] branches: ['main']
schedule: schedule:
- cron: "36 3 * * 2" - cron: '36 3 * * 2'
jobs: jobs:
eslint: eslint:

View file

@ -1,4 +1,4 @@
{ {
"js/ts.implicitProjectConfig.checkJs": true, "js/ts.implicitProjectConfig.checkJs": true,
"editor.formatOnSave": true "editor.formatOnSave": true
} }

182
README.md
View file

@ -14,36 +14,39 @@ When you extend the `WebComponent` class for your component, you only have to de
The result is a reactive UI on property changes. The result is a reactive UI on property changes.
Links: Links:
- [Read a blog explaining the reactivity](https://ayos.blog/reactive-custom-elements-with-html-dataset/) - [Read a blog explaining the reactivity](https://ayos.blog/reactive-custom-elements-with-html-dataset/)
- [View demo on CodePen](https://codepen.io/ayoayco-the-styleful/pen/ZEwoNOz?editors=1010) - [View demo on CodePen](https://codepen.io/ayoayco-the-styleful/pen/ZEwoNOz?editors=1010)
## Table of Contents ## Table of Contents
1. [Project Status](#project-status) 1. [Project Status](#project-status)
1. [Installation](#installation) 1. [Installation](#installation)
1. [Import via CDN](#import-via-cdn) 1. [Import via CDN](#import-via-cdn)
1. [Installation via npm](#installation-via-npm) 1. [Installation via npm](#installation-via-npm)
1. [Exports](#exports) 1. [Exports](#exports)
1. [Main Exports](#main-exports) 1. [Main Exports](#main-exports)
1. [Utilities](#utilities) 1. [Utilities](#utilities)
1. [Usage](#usage) 1. [Usage](#usage)
1. [Examples](#Examples) 1. [Examples](#Examples)
1. [To-Do App](#1-to-do-app) 1. [To-Do App](#1-to-do-app)
1. [Single HTML file](#2-single-html-file-example) 1. [Single HTML file](#2-single-html-file-example)
1. [Feature Demos](#3-feature-demos) 1. [Feature Demos](#3-feature-demos)
1. [`template` vs `render()`](#template-vs-render) 1. [`template` vs `render()`](#template-vs-render)
1. [Prop access](#prop-access) 1. [Prop access](#prop-access)
1. [Alternatives](#alternatives) 1. [Alternatives](#alternatives)
1. [Styling](#styling) 1. [Styling](#styling)
1. [Shadow DOM Opt-In](#shadow-dom-opt-in) 1. [Shadow DOM Opt-In](#shadow-dom-opt-in)
1. [Just the Templating](#just-the-templating) 1. [Just the Templating](#just-the-templating)
1. [Life-Cycle Hooks](#life-cycle-hooks) 1. [Life-Cycle Hooks](#life-cycle-hooks)
1. [`onInit`](#oninit) - the component is connected to the DOM, before view is initialized 1. [`onInit`](#oninit) - the component is connected to the DOM, before view is initialized
1. [`afterViewInit`](#afterviewinit) - after the view is first initialized 1. [`afterViewInit`](#afterviewinit) - after the view is first initialized
1. [`onDestroy`](#ondestroy) - the component is disconnected from the DOM 1. [`onDestroy`](#ondestroy) - the component is disconnected from the DOM
1. [`onChanges`](#onchanges) - every time an attribute value changes 1. [`onChanges`](#onchanges) - every time an attribute value changes
1. [Library Size](#library-size) 1. [Library Size](#library-size)
## Project Status ## Project Status
It is ready for many cases we see people use custom elements for. If you have a cool project built on **WebComponent.io** we'd love to know! :) It is ready for many cases we see people use custom elements for. If you have a cool project built on **WebComponent.io** we'd love to know! :)
For building some advanced interactions, we have a few issues that are still open: [#24 smart diffing](https://github.com/ayoayco/web-component-base/issues/24) & [#4 attachEffect improvements](https://github.com/ayoayco/web-component-base/issues/4) For building some advanced interactions, we have a few issues that are still open: [#24 smart diffing](https://github.com/ayoayco/web-component-base/issues/24) & [#4 attachEffect improvements](https://github.com/ayoayco/web-component-base/issues/4)
@ -53,6 +56,7 @@ In the mean time, if you have some complex needs, we recommend using the `WebCom
...or you can even [use just parts](#just-the-templating) of it for your own base class. ...or you can even [use just parts](#just-the-templating) of it for your own base class.
## Installation ## Installation
The library is distributed as complete ECMAScript Modules (ESM) and published on [NPM](https://ayco.io/n/web-component-base). Please file an issue in our [issue tracker](https://ayco.io/gh/web-component-base/issues) for problems or requests regarding our distribution. The library is distributed as complete ECMAScript Modules (ESM) and published on [NPM](https://ayco.io/n/web-component-base). Please file an issue in our [issue tracker](https://ayco.io/gh/web-component-base/issues) for problems or requests regarding our distribution.
### Import via CDN ### Import via CDN
@ -62,10 +66,11 @@ It is possible to import directly using a CDN like [esm.sh](https://esm.sh/web-c
Additionally, we use `@latest` in the rest of our [usage examples](#usage) here for simplicity, but take note that this incurs additional resolution steps for CDNs to find the actual latest published version. You may replace the `@latest` in the URL with specific versions as shown in our CodePen examples, and this will typically be better for performance. Additionally, we use `@latest` in the rest of our [usage examples](#usage) here for simplicity, but take note that this incurs additional resolution steps for CDNs to find the actual latest published version. You may replace the `@latest` in the URL with specific versions as shown in our CodePen examples, and this will typically be better for performance.
```js ```js
import { WebComponent } from "https://unpkg.com/web-component-base@latest/index.js" import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js'
``` ```
### Installation via npm ### Installation via npm
Usable for projects with bundlers or using import maps pointing to the specific files downloaded in `node_modules/web-component-base`. Usable for projects with bundlers or using import maps pointing to the specific files downloaded in `node_modules/web-component-base`.
```bash ```bash
@ -81,31 +86,37 @@ You can import everything separately, or in a single file each for the main expo
```js ```js
// all in a single file // all in a single file
import { WebComponent, html, attachEffect } from "web-component-base"; import { WebComponent, html, attachEffect } from 'web-component-base'
// in separate files // in separate files
import { WebComponent } from "web-component-base/WebComponent.js"; import { WebComponent } from 'web-component-base/WebComponent.js'
import { html } from "web-component-base/html.js"; import { html } from 'web-component-base/html.js'
import { attachEffect } from "web-component-base/attach-effect.js"; import { attachEffect } from 'web-component-base/attach-effect.js'
``` ```
### Utilities ### Utilities
```js ```js
// in a single file // in a single file
import { serialize, deserialize, getCamelCase, getKebabCase, createElement } from "web-component-base/utils"; import {
serialize,
deserialize,
getCamelCase,
getKebabCase,
createElement,
} from 'web-component-base/utils'
// or separate files // or separate files
import { serialize } from "web-component-base/utils/serialize.js"; import { serialize } from 'web-component-base/utils/serialize.js'
import { createElement } from "web-component-base/utils/create-element.js"; import { createElement } from 'web-component-base/utils/create-element.js'
// etc... // etc...
``` ```
## Usage ## Usage
@ -114,21 +125,21 @@ In your component class:
```js ```js
// HelloWorld.mjs // HelloWorld.mjs
import { WebComponent } from "https://unpkg.com/web-component-base@latest/index.js"; import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js'
class HelloWorld extends WebComponent { class HelloWorld extends WebComponent {
static props ={ static props = {
myName: 'World', myName: 'World',
emotion: 'sad' emotion: 'sad',
} }
get template() { get template() {
return ` return `
<h1>Hello ${this.props.myName}${this.props.emotion === "sad" ? ". 😭" : "! 🙌"}</h1> <h1>Hello ${this.props.myName}${this.props.emotion === 'sad' ? '. 😭' : '! 🙌'}</h1>
`; `
} }
} }
customElements.define('hello-world', HelloWorld); customElements.define('hello-world', HelloWorld)
``` ```
In your HTML page: In your HTML page:
@ -163,40 +174,42 @@ A simple app that allows adding / completing tasks:
Here is an example of using a custom element in a single .html file. Here is an example of using a custom element in a single .html file.
```html ```html
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<title>WC Base Test</title> <title>WC Base Test</title>
<script type="module"> <script type="module">
import { WebComponent } from "https://unpkg.com/web-component-base@latest/index.js"; import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js'
class HelloWorld extends WebComponent { class HelloWorld extends WebComponent {
static props = { static props = {
myName: 'World' myName: 'World',
} }
get template() { get template() {
return `<h1>Hello ${this.props.myName}!</h1>`; return `<h1>Hello ${this.props.myName}!</h1>`
} }
} }
customElements.define("hello-world", HelloWorld); customElements.define('hello-world', HelloWorld)
</script> </script>
</head> </head>
<body> <body>
<hello-world my-name="Ayo"></hello-world> <hello-world my-name="Ayo"></hello-world>
<script> <script>
const helloWorld = document.querySelector('hello-world'); const helloWorld = document.querySelector('hello-world')
setTimeout(() => { setTimeout(() => {
helloWorld.props.myName = 'Ayo zzzZzzz'; helloWorld.props.myName = 'Ayo zzzZzzz'
}, 2500); }, 2500)
</script> </script>
</body> </body>
</html> </html>
``` ```
### 3. Feature Demos ### 3. Feature Demos
Some feature-specific demos: Some feature-specific demos:
1. [Context-Aware Post-Apocalyptic Human](https://codepen.io/ayoayco-the-styleful/pen/WNqJMNG?editors=1010)
1. [Context-Aware Post-Apocalyptic Human](https://codepen.io/ayoayco-the-styleful/pen/WNqJMNG?editors=1010)
1. [Simple reactive property](https://codepen.io/ayoayco-the-styleful/pen/ZEwoNOz?editors=1010) 1. [Simple reactive property](https://codepen.io/ayoayco-the-styleful/pen/ZEwoNOz?editors=1010)
1. [Counter & Toggle](https://codepen.io/ayoayco-the-styleful/pen/PoVegBK?editors=1010) 1. [Counter & Toggle](https://codepen.io/ayoayco-the-styleful/pen/PoVegBK?editors=1010)
1. [Using custom templating (lit-html)](https://codepen.io/ayoayco-the-styleful/pen/ZEwNJBR?editors=1010) 1. [Using custom templating (lit-html)](https://codepen.io/ayoayco-the-styleful/pen/ZEwNJBR?editors=1010)
@ -209,25 +222,23 @@ Some feature-specific demos:
This mental model attempts to reduce the cognitive complexity of authoring components: This mental model attempts to reduce the cognitive complexity of authoring components:
1. The `template` is a read-only property (initialized with a `get` keyword) that represents *how* the component view is rendered. 1. The `template` is a read-only property (initialized with a `get` keyword) that represents _how_ the component view is rendered.
1. There is a `render()` method that triggers a view render. 1. There is a `render()` method that triggers a view render.
1. This `render()` method is *automatically* called under the hood every time an attribute value changed. 1. This `render()` method is _automatically_ called under the hood every time an attribute value changed.
1. You can *optionally* call this `render()` method at any point to trigger a render if you need (eg, if you have private unobserved properties that need to manually trigger a render) 1. You can _optionally_ call this `render()` method at any point to trigger a render if you need (eg, if you have private unobserved properties that need to manually trigger a render)
1. Overriding the `render()` function for handling a custom `template` is also possible. Here's an example of using `lit-html`: [View on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/ZEwNJBR?editors=1010) 1. Overriding the `render()` function for handling a custom `template` is also possible. Here's an example of using `lit-html`: [View on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/ZEwNJBR?editors=1010)
## Prop Access ## Prop Access
The `props` property of the `WebComponent` interface is provided for easy read/write access to a camelCase counterpart of *any* observed attribute. The `props` property of the `WebComponent` interface is provided for easy read/write access to a camelCase counterpart of _any_ observed attribute.
```js ```js
class HelloWorld extends WebComponent { class HelloWorld extends WebComponent {
static props = { static props = {
myProp: 'World' myProp: 'World',
} }
get template() { get template() {
return html` return html` <h1>Hello ${this.props.myProp}</h1> `
<h1>Hello ${this.props.myProp}</h1>
`;
} }
} }
``` ```
@ -235,11 +246,13 @@ class HelloWorld extends WebComponent {
Assigning a value to the `props.camelCase` counterpart of an observed attribute will trigger an "attribute change" hook. Assigning a value to the `props.camelCase` counterpart of an observed attribute will trigger an "attribute change" hook.
For example, assigning a value like so: For example, assigning a value like so:
``` ```
this.props.myName = 'hello' this.props.myName = 'hello'
``` ```
...is like calling the following: ...is like calling the following:
``` ```
this.setAttribute('my-name','hello'); this.setAttribute('my-name','hello');
``` ```
@ -253,6 +266,7 @@ Therefore, this will tell the browser that the UI needs a render if the attribut
### Alternatives ### Alternatives
The current alternatives are using what `HTMLElement` provides out-of-the-box, which are: The current alternatives are using what `HTMLElement` provides out-of-the-box, which are:
1. `HTMLElement.dataset` for attributes prefixed with `data-*`. Read more about this [on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset). 1. `HTMLElement.dataset` for attributes prefixed with `data-*`. Read more about this [on MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset).
1. Methods for reading/writing attribute values: `setAttribute(...)` and `getAttribute(...)`; note that managing the attribute names as strings can be difficult as the code grows. 1. Methods for reading/writing attribute values: `setAttribute(...)` and `getAttribute(...)`; note that managing the attribute names as strings can be difficult as the code grows.
@ -261,56 +275,58 @@ The current alternatives are using what `HTMLElement` provides out-of-the-box, w
When using the built-in `html` function for tagged templates, a style object of type `Partial<CSSStyleDeclaration>` can be passed to any element's `style` attribute. This allows for calculated and conditional styles. Read more on style objects [on MDN](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration). When using the built-in `html` function for tagged templates, a style object of type `Partial<CSSStyleDeclaration>` can be passed to any element's `style` attribute. This allows for calculated and conditional styles. Read more on style objects [on MDN](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleDeclaration).
Try it now with this [example on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/bGzXjwQ?editors=1010) Try it now with this [example on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/bGzXjwQ?editors=1010)
```js ```js
import { WebComponent } from "https://unpkg.com/web-component-base@latest/index.js"; import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js'
class StyledElements extends WebComponent { class StyledElements extends WebComponent {
static props = { static props = {
emphasize: false, emphasize: false,
type: "warn", type: 'warn',
}; }
#typeStyles = { #typeStyles = {
warn: { warn: {
backgroundColor: "yellow", backgroundColor: 'yellow',
border: "1px solid orange", border: '1px solid orange',
}, },
error: { error: {
backgroundColor: "orange", backgroundColor: 'orange',
border: "1px solid red", border: '1px solid red',
}, },
}; }
get template() { get template() {
return html` return html`
<div <div
style=${{ style=${{
...this.#typeStyles[this.props.type], ...this.#typeStyles[this.props.type],
padding: "1em", padding: '1em',
}} }}
> >
<p style=${{ fontStyle: this.props.emphasize && "italic" }}>Wow!</p> <p style=${{ fontStyle: this.props.emphasize && 'italic' }}>Wow!</p>
</div> </div>
`; `
} }
} }
customElements.define("styled-elements", StyledElements); customElements.define('styled-elements', StyledElements)
``` ```
## Shadow DOM Opt-In ## Shadow DOM Opt-In
Add a static property `shadowRootInit` with object value of type `ShadowRootInit` (see [options on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#options)) to opt-in to using shadow dom for the whole component. Add a static property `shadowRootInit` with object value of type `ShadowRootInit` (see [options on MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#options)) to opt-in to using shadow dom for the whole component.
Try it now [on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/VwRYVPv?editors=1010) Try it now [on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/VwRYVPv?editors=1010)
Example: Example:
```js ```js
static shadowRootInit = { static shadowRootInit = {
mode: "closed", mode: "closed",
}; };
``` ```
## Just the Templating ## Just the Templating
You don't have to extend the whole base class just to use some features. All internals are exposed and usable separately so you can practically build the behavior on your own classes. You don't have to extend the whole base class just to use some features. All internals are exposed and usable separately so you can practically build the behavior on your own classes.
@ -318,110 +334,112 @@ You don't have to extend the whole base class just to use some features. All int
Here's an example of using the `html` tag template on a class that extends from vanilla `HTMLElement`... also [View on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/bGzJQJg?editors=1010). Here's an example of using the `html` tag template on a class that extends from vanilla `HTMLElement`... also [View on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/bGzJQJg?editors=1010).
```js ```js
import {html} from 'https://unpkg.com/web-component-base/html' import { html } from 'https://unpkg.com/web-component-base/html'
import {createElement} from 'https://unpkg.com/web-component-base/utils' import { createElement } from 'https://unpkg.com/web-component-base/utils'
class MyQuote extends HTMLElement { class MyQuote extends HTMLElement {
connectedCallback() { connectedCallback() {
const el = createElement(html` const el = createElement(
<button onClick=${() => alert('hey')}> html` <button onClick=${() => alert('hey')}>hey</button>`
hey )
</button>`);
this.appendChild(el) this.appendChild(el)
} }
} }
customElements.define('my-quote', MyQuote) customElements.define('my-quote', MyQuote)
``` ```
## Life-Cycle Hooks ## Life-Cycle Hooks
Define behavior when certain events in the component's life cycle is triggered by providing hook methods Define behavior when certain events in the component's life cycle is triggered by providing hook methods
### onInit() ### onInit()
- Triggered when the component is connected to the DOM - Triggered when the component is connected to the DOM
- Best for setting up the component - Best for setting up the component
```js ```js
import { WebComponent } from "https://unpkg.com/web-component-base@latest/index.js"; import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js'
class ClickableText extends WebComponent { class ClickableText extends WebComponent {
// gets called when the component is used in an HTML document // gets called when the component is used in an HTML document
onInit() { onInit() {
this.onclick = () => console.log(">>> click!"); this.onclick = () => console.log('>>> click!')
} }
get template() { get template() {
return `<span style="cursor:pointer">Click me!</span>`; return `<span style="cursor:pointer">Click me!</span>`
} }
} }
``` ```
### afterViewInit() ### afterViewInit()
- Triggered after the view is first initialized
- Triggered after the view is first initialized
```js ```js
class ClickableText extends WebComponent { class ClickableText extends WebComponent {
// gets called when the component's innerHTML is first filled // gets called when the component's innerHTML is first filled
afterViewInit() { afterViewInit() {
const footer = this.querySelector('footer'); const footer = this.querySelector('footer')
// do stuff to footer after view is initialized // do stuff to footer after view is initialized
} }
get template() { get template() {
return `<footer>Awesome site &copy; 2023</footer>`; return `<footer>Awesome site &copy; 2023</footer>`
} }
} }
``` ```
### onDestroy() ### onDestroy()
- Triggered when the component is disconnected from the DOM - Triggered when the component is disconnected from the DOM
- best for undoing any setup done in `onInit()` - best for undoing any setup done in `onInit()`
```js ```js
import { WebComponent } from "https://unpkg.com/web-component-base@latest/index.js"; import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js'
class ClickableText extends WebComponent { class ClickableText extends WebComponent {
clickCallback() { clickCallback() {
console.log(">>> click!"); console.log('>>> click!')
} }
onInit() { onInit() {
this.onclick = this.clickCallback; this.onclick = this.clickCallback
} }
onDestroy() { onDestroy() {
console.log(">>> removing event listener"); console.log('>>> removing event listener')
this.removeEventListener("click", this.clickCallback); this.removeEventListener('click', this.clickCallback)
} }
get template() { get template() {
return `<span style="cursor:pointer">Click me!</span>`; return `<span style="cursor:pointer">Click me!</span>`
} }
} }
``` ```
### onChanges() ### onChanges()
- Triggered when an attribute value changed - Triggered when an attribute value changed
```js ```js
import { WebComponent } from "https://unpkg.com/web-component-base@latest/index.js"; import { WebComponent } from 'https://unpkg.com/web-component-base@latest/index.js'
class ClickableText extends WebComponent { class ClickableText extends WebComponent {
// gets called when an attribute value changes // gets called when an attribute value changes
onChanges(changes) { onChanges(changes) {
const {property, previousValue, currentValue} = changes; const { property, previousValue, currentValue } = changes
console.log('>>> ', {property, previousValue, currentValue}) console.log('>>> ', { property, previousValue, currentValue })
} }
get template() { get template() {
return `<span style="cursor:pointer">Click me!</span>`; return `<span style="cursor:pointer">Click me!</span>`
} }
} }
``` ```
## Library Size ## Library Size
All the functions and the base class in the library are minimalist by design and only contains what is needed for their purpose. All the functions and the base class in the library are minimalist by design and only contains what is needed for their purpose.

View file

@ -1,5 +1,5 @@
import globals from "globals"; import globals from 'globals'
import pluginJs from "@eslint/js"; import pluginJs from '@eslint/js'
/** @type {import('eslint').Linter.Config[]} */ /** @type {import('eslint').Linter.Config[]} */
export default [ export default [
@ -7,10 +7,10 @@ export default [
pluginJs.configs.recommended, pluginJs.configs.recommended,
{ {
rules: { rules: {
"no-unused-vars": "warn", 'no-unused-vars': 'warn',
}, },
}, },
{ {
ignores: ["site/*"], ignores: ['site/*'],
}, },
]; ]

View file

@ -1,21 +1,23 @@
// @ts-check // @ts-check
import { WebComponent, attachEffect, html } from "../../src/index.js"; import { WebComponent, attachEffect, html } from '../../src/index.js'
export class Counter extends WebComponent { export class Counter extends WebComponent {
static props = { static props = {
count: 0, count: 0,
}; }
onInit() { onInit() {
attachEffect(this.props.count, (count) => console.log(count)); attachEffect(this.props.count, (count) => console.log(count))
} }
afterViewInit() { afterViewInit() {
attachEffect(this.props.count, (count) => console.log(count + 100)); attachEffect(this.props.count, (count) => console.log(count + 100))
} }
get template() { get template() {
return html`<button onclick=${() => ++this.props.count} id="btn">${this.props.count}</button>`; return html`<button onclick=${() => ++this.props.count} id="btn">
${this.props.count}
</button>`
} }
} }
customElements.define("my-counter", Counter); customElements.define('my-counter', Counter)

View file

@ -1,22 +1,24 @@
// @ts-check // @ts-check
import { WebComponent, attachEffect, html } from "../../src/index.js"; import { WebComponent, attachEffect, html } from '../../src/index.js'
export class Decrease extends WebComponent { export class Decrease extends WebComponent {
static props = { static props = {
count: 999, count: 999,
}; }
onInit() { onInit() {
attachEffect(this.props.count, (count) => console.log(count)); attachEffect(this.props.count, (count) => console.log(count))
} }
afterViewInit() { afterViewInit() {
attachEffect(this.props.count, (count) => console.log(count + 100)); attachEffect(this.props.count, (count) => console.log(count + 100))
} }
get template() { get template() {
return html`<button onclick=${() => --this.props.count} id="btn">${this.props.count}</button>`; return html`<button onclick=${() => --this.props.count} id="btn">
${this.props.count}
</button>`
} }
} }
customElements.define("my-decrement", Decrease); customElements.define('my-decrement', Decrease)

View file

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />

View file

@ -1,16 +1,17 @@
import { html, WebComponent } from "../../src/index.js"; import { html, WebComponent } from '../../src/index.js'
export class BooleanPropTest extends WebComponent { export class BooleanPropTest extends WebComponent {
static props = { static props = {
isInline: false, isInline: false,
anotherone: false, anotherone: false,
}; }
get template() { get template() {
return html` return html`
<p>is-inline: ${this.props.isInline}</p><p>another-one: ${this.props.anotherone}</p> <p>is-inline: ${this.props.isInline}</p>
`; <p>another-one: ${this.props.anotherone}</p>
`
} }
} }
customElements.define("boolean-prop-test", BooleanPropTest); customElements.define('boolean-prop-test', BooleanPropTest)

View file

@ -1,17 +1,17 @@
// @ts-check // @ts-check
import { WebComponent, html } from "../../src/index.js"; import { WebComponent, html } from '../../src/index.js'
export class Counter extends WebComponent { export class Counter extends WebComponent {
static props = { static props = {
count: 0, count: 0,
}; }
get template() { get template() {
return html` return html`
<button onClick=${() => ++this.props.count} id="btn"> <button onClick=${() => ++this.props.count} id="btn">
${this.props.count} ${this.props.count}
</button> </button>
`; `
} }
} }
customElements.define("my-counter", Counter); customElements.define('my-counter', Counter)

View file

@ -1,24 +1,26 @@
// @ts-check // @ts-check
import { html, WebComponent } from "../../src/index.js"; import { html, WebComponent } from '../../src/index.js'
export class HelloWorld extends WebComponent { export class HelloWorld extends WebComponent {
static props = { static props = {
count: 0, count: 0,
emotion: "sad", emotion: 'sad',
}; }
onInit() { onInit() {
this.props.count = 0; this.props.count = 0
} }
get template() { get template() {
const label = this.props.count ? `Clicked ${this.props.count}` : "World"; const label = this.props.count ? `Clicked ${this.props.count}` : 'World'
const emote = this.props.emotion === "sad" ? ". 😭" : "! 🙌"; const emote = this.props.emotion === 'sad' ? '. 😭' : '! 🙌'
return html` return html`
<button onclick=${() => ++this.props.count}>Hello ${label}${emote}</button> <button onclick=${() => ++this.props.count}>
Hello ${label}${emote}
</button>
` `
} }
} }
customElements.define("hello-world", HelloWorld); customElements.define('hello-world', HelloWorld)

View file

@ -1,19 +1,21 @@
// @ts-check // @ts-check
import { html, WebComponent } from "../../src/index.js"; import { html, WebComponent } from '../../src/index.js'
class SimpleText extends WebComponent { class SimpleText extends WebComponent {
clickCallback() { clickCallback() {
console.log(">>> click!"); console.log('>>> click!')
} }
onDestroy() { onDestroy() {
console.log(">>> removing event listener"); console.log('>>> removing event listener')
this.removeEventListener("click", this.clickCallback); this.removeEventListener('click', this.clickCallback)
} }
get template() { get template() {
return html`<span onclick=${this.clickCallback} style="cursor:pointer">Click me!</span>`; return html`<span onclick=${this.clickCallback} style="cursor:pointer"
>Click me!</span
>`
} }
} }
customElements.define("simple-text", SimpleText); customElements.define('simple-text', SimpleText)

View file

@ -1,16 +1,16 @@
import { WebComponent, html } from "../../src/index.js"; import { WebComponent, html } from '../../src/index.js'
class Toggle extends WebComponent { class Toggle extends WebComponent {
static props = { static props = {
toggle: false, toggle: false,
}; }
get template() { get template() {
return html` return html`
<button onClick=${() => (this.props.toggle = !this.props.toggle)}> <button onClick=${() => (this.props.toggle = !this.props.toggle)}>
${this.props.toggle} ${this.props.toggle}
</button> </button>
`; `
} }
} }
customElements.define("my-toggle", Toggle); customElements.define('my-toggle', Toggle)

View file

@ -24,10 +24,10 @@
</p> </p>
<script type="module"> <script type="module">
const helloWorld = document.querySelector("hello-world"); const helloWorld = document.querySelector('hello-world')
setTimeout(() => { setTimeout(() => {
helloWorld.props.emotion = "excited"; helloWorld.props.emotion = 'excited'
}, 2500); }, 2500)
</script> </script>
</body> </body>
</html> </html>

View file

@ -13,33 +13,35 @@
import { import {
WebComponent, WebComponent,
html, html,
} from "https://esm.sh/web-component-base@latest"; } from 'https://esm.sh/web-component-base@latest'
export class Counter extends WebComponent { export class Counter extends WebComponent {
static props = { static props = {
count: 0, count: 0,
}; }
get template() { get template() {
return html`<button onClick=${() => ++this.props.count}> return html`<button onClick=${() => ++this.props.count}>
${this.props.count} ${this.props.count}
</button>`; </button>`
} }
} }
class Toggle extends WebComponent { class Toggle extends WebComponent {
static props = { static props = {
toggle: false, toggle: false,
}; }
clickFn = () => (this.props.toggle = !this.props.toggle); clickFn = () => (this.props.toggle = !this.props.toggle)
get template() { get template() {
return html`<button onclick=${this.clickFn}>${this.props.toggle ? "On" : "Off"}</button>`; return html`<button onclick=${this.clickFn}>
${this.props.toggle ? 'On' : 'Off'}
</button>`
} }
} }
customElements.define("my-counter", Counter); customElements.define('my-counter', Counter)
customElements.define("my-toggle", Toggle); customElements.define('my-toggle', Toggle)
</script> </script>
</head> </head>
<body> <body>

View file

@ -1,12 +1,12 @@
import { html, WebComponent } from "../../src/index.js"; import { html, WebComponent } from '../../src/index.js'
export class HelloWorld extends WebComponent { export class HelloWorld extends WebComponent {
static props = { static props = {
myName: "World", myName: 'World',
}; }
get template() { get template() {
return html`<p>Hello ${this.props.myName}</p>`; return html`<p>Hello ${this.props.myName}</p>`
} }
} }
customElements.define("hello-world", HelloWorld); customElements.define('hello-world', HelloWorld)

View file

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />

View file

@ -1,12 +1,14 @@
import { html, WebComponent } from "../../src/index.js"; import { html, WebComponent } from '../../src/index.js'
export class Counter extends WebComponent { export class Counter extends WebComponent {
static props = { static props = {
count: 123, count: 123,
}; }
get template() { get template() {
return html`<button onclick=${() => ++this.props.count} id="btn">${this.props.count}</button>`; return html`<button onclick=${() => ++this.props.count} id="btn">
${this.props.count}
</button>`
} }
} }
customElements.define("my-counter", Counter); customElements.define('my-counter', Counter)

View file

@ -1,39 +1,39 @@
// @ts-check // @ts-check
import { WebComponent, html } from "../../src/index.js"; import { WebComponent, html } from '../../src/index.js'
class StyledElements extends WebComponent { class StyledElements extends WebComponent {
static props = { static props = {
condition: false, condition: false,
type: "info", type: 'info',
}; }
#typeStyles = { #typeStyles = {
info: { info: {
backgroundColor: "blue", backgroundColor: 'blue',
border: "1px solid green", border: '1px solid green',
}, },
warn: { warn: {
backgroundColor: "yellow", backgroundColor: 'yellow',
border: "1px solid orange", border: '1px solid orange',
}, },
error: { error: {
backgroundColor: "orange", backgroundColor: 'orange',
border: "1px solid red", border: '1px solid red',
}, },
}; }
get template() { get template() {
return html` return html`
<div <div
style=${{ style=${{
...this.#typeStyles[this.props.type], ...this.#typeStyles[this.props.type],
padding: "1em", padding: '1em',
}} }}
> >
<p style=${{ fontStyle: this.props.condition && "italic" }}>Wow!</p> <p style=${{ fontStyle: this.props.condition && 'italic' }}>Wow!</p>
</div> </div>
`; `
} }
} }
customElements.define("styled-elements", StyledElements); customElements.define('styled-elements', StyledElements)

View file

@ -1,22 +1,22 @@
// @ts-check // @ts-check
import { WebComponent, html } from "../../src/index.js"; import { WebComponent, html } from '../../src/index.js'
export class Counter extends WebComponent { export class Counter extends WebComponent {
static props = { static props = {
count: 123, count: 123,
}; }
get template() { get template() {
const list = ["a", "b", "c", "what"]; const list = ['a', 'b', 'c', 'what']
const links = [ const links = [
{ {
url: "https://ayco.io", url: 'https://ayco.io',
text: "Ayo Ayco", text: 'Ayo Ayco',
}, },
{ {
url: "https://ayco.io/gh/McFly", url: 'https://ayco.io/gh/McFly',
text: "McFly", text: 'McFly',
}, },
]; ]
return html` return html`
<button <button
@ -39,11 +39,11 @@ export class Counter extends WebComponent {
<ul> <ul>
${links.map( ${links.map(
(link) => (link) =>
html`<li><a href=${link.url} target="_blank">${link.text}</a></li>`, html`<li><a href=${link.url} target="_blank">${link.text}</a></li>`
)} )}
</ul> </ul>
`; `
} }
} }
customElements.define("my-counter", Counter); customElements.define('my-counter', Counter)

View file

@ -1,25 +1,25 @@
import { WebComponent } from "../../src/index.js"; import { WebComponent } from '../../src/index.js'
import { import {
html, html,
render as lit, render as lit,
} from "https://unpkg.com/lit-html@3.1.0/lit-html.js"; } from 'https://unpkg.com/lit-html@3.1.0/lit-html.js'
export class LitCounter extends WebComponent { export class LitCounter extends WebComponent {
static props = { static props = {
count: 123, count: 123,
}; }
get template() { get template() {
const list = ["a", "b", "c", "what"]; const list = ['a', 'b', 'c', 'what']
const links = [ const links = [
{ {
url: "https://ayco.io", url: 'https://ayco.io',
text: "Ayo Ayco", text: 'Ayo Ayco',
}, },
{ {
url: "https://ayco.io/gh/McFly", url: 'https://ayco.io/gh/McFly',
text: "McFly", text: 'McFly',
}, },
]; ]
return html` return html`
<button <button
@ -42,14 +42,14 @@ export class LitCounter extends WebComponent {
<ul> <ul>
${links.map( ${links.map(
(link) => (link) =>
html`<li><a href=${link.url} target="_blank">${link.text}</a></li>`, html`<li><a href=${link.url} target="_blank">${link.text}</a></li>`
)} )}
</ul> </ul>
`; `
} }
render() { render() {
lit(this.template, this); lit(this.template, this)
} }
} }
customElements.define("lit-counter", LitCounter); customElements.define('lit-counter', LitCounter)

View file

@ -1,13 +1,15 @@
// @ts-check // @ts-check
import { html, WebComponent } from "../../src/index.js"; import { html, WebComponent } from '../../src/index.js'
export class Counter extends WebComponent { export class Counter extends WebComponent {
static props = { static props = {
count: 1, count: 1,
}; }
get template() { get template() {
return html`<button onclick=${() => ++this.props.count}>${this.props.count}</button>`; return html`<button onclick=${() => ++this.props.count}>
${this.props.count}
</button>`
} }
} }
customElements.define("my-counter", Counter); customElements.define('my-counter', Counter)

View file

@ -1,14 +1,14 @@
import { html, WebComponent } from "../../src/index.js"; import { html, WebComponent } from '../../src/index.js'
export class HelloWorld extends WebComponent { export class HelloWorld extends WebComponent {
static props = { static props = {
name: "a", name: 'a',
}; }
addA = () => (this.props.name += "a"); addA = () => (this.props.name += 'a')
get template() { get template() {
return html`<button onclick=${this.addA}>W${this.props.name}h!</button>`; return html`<button onclick=${this.addA}>W${this.props.name}h!</button>`
} }
} }
customElements.define("my-hello-world", HelloWorld); customElements.define('my-hello-world', HelloWorld)

View file

@ -1,4 +1,4 @@
import { html, WebComponent } from "../../src/index.js"; import { html, WebComponent } from '../../src/index.js'
/** /**
* TODO: rendering currently wipes all children so focus gets removed on fields * TODO: rendering currently wipes all children so focus gets removed on fields
@ -6,45 +6,43 @@ import { html, WebComponent } from "../../src/index.js";
export class ObjectText extends WebComponent { export class ObjectText extends WebComponent {
static props = { static props = {
object: { object: {
hello: "worldzz", hello: 'worldzz',
age: 2, age: 2,
}, },
}; }
onChanges() { onChanges() {
console.log(">>> object", this.props.object); console.log('>>> object', this.props.object)
} }
get template() { get template() {
return html` return html`
<form> <form>
<label for="greeting-field">Hello</label> <label for="greeting-field">Hello</label>
<textarea <textarea
onkeyup=${ onkeyup=${(event) => {
(event) => { this.props.object = {
this.props.object = { ...this.props.object,
...this.props.object, hello: event.target.value,
hello: event.target.value,
};
} }
} }}
id="greeting-field"> id="greeting-field"
>
${this.props.object.hello} ${this.props.object.hello}
</textarea> </textarea
>
<label for="age-field">Age</label> <label for="age-field">Age</label>
<input <input
onkeyup=${ onkeyup=${(event) => {
(event) => { this.props.object = {
this.props.object = { ...this.props.object,
...this.props.object, age: event.target.value,
age: event.target.value,
};
} }
} }}
id="age-field" value=${this.props.object.age} /> id="age-field"
value=${this.props.object.age}
/>
</form> </form>
`; `
} }
} }
customElements.define("my-object", ObjectText); customElements.define('my-object', ObjectText)

View file

@ -1,18 +1,20 @@
// @ts-check // @ts-check
import { html, WebComponent } from "../../src/index.js"; import { html, WebComponent } from '../../src/index.js'
export class Toggle extends WebComponent { export class Toggle extends WebComponent {
static props = { static props = {
toggle: false, toggle: false,
}; }
handleToggle() { handleToggle() {
this.props.toggle = !this.props.toggle; this.props.toggle = !this.props.toggle
} }
get template() { get template() {
return html` return html`
<button onclick=${() => this.handleToggle()} id="toggle">${this.props.toggle ? "On" : "Off"}</button> <button onclick=${() => this.handleToggle()} id="toggle">
`; ${this.props.toggle ? 'On' : 'Off'}
</button>
`
} }
} }
customElements.define("my-toggle", Toggle); customElements.define('my-toggle', Toggle)

View file

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@ -10,33 +10,27 @@
<script type="module" src="./Object.mjs"></script> <script type="module" src="./Object.mjs"></script>
<style> <style>
* { * {
font-size: larger font-size: larger;
} }
</style> </style>
</head> </head>
<body> <body>
<div> <div>Counter: <my-counter></my-counter></div>
Counter: <my-counter></my-counter> <div>Toggle: <my-toggle></my-toggle></div>
</div> <div>String: <my-hello-world></my-hello-world></div>
<div>
Toggle: <my-toggle></my-toggle>
</div>
<div>
String: <my-hello-world></my-hello-world>
</div>
<div> <div>
<my-object></my-object> <my-object></my-object>
<p id="display-panel"></p> <p id="display-panel"></p>
</div> </div>
<script type="module"> <script type="module">
import { attachEffect } from "../../src/index.js"; import { attachEffect } from '../../src/index.js'
const myObjectEl = document.querySelector('my-object'); const myObjectEl = document.querySelector('my-object')
const objectProp = myObjectEl.props.object; const objectProp = myObjectEl.props.object
const displayPanelEl = document.querySelector('#display-panel'); const displayPanelEl = document.querySelector('#display-panel')
displayPanelEl.textContent = JSON.stringify(objectProp); displayPanelEl.textContent = JSON.stringify(objectProp)
attachEffect(objectProp, (object) => { attachEffect(objectProp, (object) => {
displayPanelEl.textContent = JSON.stringify(object); displayPanelEl.textContent = JSON.stringify(object)
}); })
</script> </script>
</body> </body>
</html> </html>

View file

@ -1,33 +1,33 @@
// @ts-check // @ts-check
import { WebComponent, html } from "../../src/index.js"; import { WebComponent, html } from '../../src/index.js'
export class Counter extends WebComponent { export class Counter extends WebComponent {
static props = { static props = {
count: 123, count: 123,
}; }
static shadowRootInit = { static shadowRootInit = {
mode: "open", mode: 'open',
}; }
get template() { get template() {
const list = ["a", "b", "c", "what"]; const list = ['a', 'b', 'c', 'what']
const links = [ const links = [
{ {
url: "https://ayco.io", url: 'https://ayco.io',
text: "Ayo Ayco", text: 'Ayo Ayco',
}, },
{ {
url: "https://ayco.io/gh/McFly", url: 'https://ayco.io/gh/McFly',
text: "McFly", text: 'McFly',
}, },
]; ]
return html` return html`
<button <button
class="hey" class="hey"
id="btn" id="btn"
onClick=${() => ++this.props.count} onClick=${() => ++this.props.count}
style=${{ backgroundColor: "green", color: "white" }} style=${{ backgroundColor: 'green', color: 'white' }}
about="Elephant" about="Elephant"
data-name="thing" data-name="thing"
aria-name="thingz" aria-name="thingz"
@ -48,8 +48,8 @@ export class Counter extends WebComponent {
</li>` </li>`
)} )}
</ul> </ul>
`; `
} }
} }
customElements.define("my-counter", Counter); customElements.define('my-counter', Counter)

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,3 @@
packages: packages:
# include packages in subfolders (e.g. apps/ and packages/) # include packages in subfolders (e.g. apps/ and packages/)
- "site/**" - 'site/**'

View file

@ -1,8 +1,7 @@
# McFly Starter Project # McFly Starter Project
## Background ## Background
This project was generated from the basic template for **McFly** -- a no-framework framework that assists in leveraging the web platform. This project was generated from the basic template for **McFly** -- a no-framework framework that assists in leveraging the web platform.
![template-basic](https://raw.githubusercontent.com/ayoayco/McFly/main/assets/template-basic.png) ![template-basic](https://raw.githubusercontent.com/ayoayco/McFly/main/assets/template-basic.png)
@ -10,6 +9,7 @@ This project was generated from the basic template for **McFly** -- a no-framewo
It contains example files to get you started using vanilla web technologies in a modern way. See the [Special Directories](#special-directories) section for more information. It contains example files to get you started using vanilla web technologies in a modern way. See the [Special Directories](#special-directories) section for more information.
## Features ## Features
The time has come for vanilla Web tech. 🎉 The time has come for vanilla Web tech. 🎉
✅ Create web apps with vanilla custom elements<br> ✅ Create web apps with vanilla custom elements<br>
@ -19,17 +19,21 @@ The time has come for vanilla Web tech. 🎉
✅ Deploy anywhere<br> ✅ Deploy anywhere<br>
## Special directories ## Special directories
**1. `./src/pages/`** **1. `./src/pages/`**
- file-based routing for `.html` files - file-based routing for `.html` files
- directly use custom elements & static fragments (no imports or registry maintenance needed) - directly use custom elements & static fragments (no imports or registry maintenance needed)
- use `<script server:setup>` to define logic that runs on the server, which then gets stripped away - use `<script server:setup>` to define logic that runs on the server, which then gets stripped away
**2. `./src/components/`** **2. `./src/components/`**
- custom element constructor files (only `.js` files for now) - custom element constructor files (only `.js` files for now)
- all components are automatically registered using their file names; a `hello-world.js` component can be used as `<hello-world>` - all components are automatically registered using their file names; a `hello-world.js` component can be used as `<hello-world>`
- static `.html` fragments; a `my-header.html` fragment can be directly used as `<my-header>` - static `.html` fragments; a `my-header.html` fragment can be directly used as `<my-header>`
**3. `./routes/api/`** **3. `./routes/api/`**
- file-based routing for REST API endpoints - file-based routing for REST API endpoints
- e.g., `./routes/api/users.ts` can be accessed via `http://<domain>/api/users` - e.g., `./routes/api/users.ts` can be accessed via `http://<domain>/api/users`
- TypeScript or JavaScript welcome! - TypeScript or JavaScript welcome!
@ -39,25 +43,25 @@ The time has come for vanilla Web tech. 🎉
To tell McFly you want to use components, pass the mode (only `"js"` for now) to the `components` prop mcfly.config.ts To tell McFly you want to use components, pass the mode (only `"js"` for now) to the `components` prop mcfly.config.ts
```js ```js
import defineConfig from "./packages/define-config"; import defineConfig from './packages/define-config'
export default defineConfig({ export default defineConfig({
components: "js", components: 'js',
}); })
``` ```
## Commands ## Commands
The following commands are available to you on this project. Add more, or modify them as needed in your `./package.json` file. The following commands are available to you on this project. Add more, or modify them as needed in your `./package.json` file.
| Command | Action | | Command | Action |
| --- | --- | | --------------- | ---------------------------------------------------- |
| npm start | Start the development server | | npm start | Start the development server |
| npm run prepare | Prepare the workspace | | npm run prepare | Prepare the workspace |
| npm run build | Locally generate the app's build files to `./output` | | npm run build | Locally generate the app's build files to `./output` |
| npm run preview | Preview the built app locally | | npm run preview | Preview the built app locally |
--- ---
*Just keep building*<br />
*A project by [Ayo Ayco](https://ayco.io)* _Just keep building_<br />
_A project by [Ayo Ayco](https://ayco.io)_

View file

@ -1,4 +1,4 @@
import { defineMcFlyConfig } from "#imports"; import { defineMcFlyConfig } from '#imports'
export default defineMcFlyConfig({ export default defineMcFlyConfig({
components: "js", components: 'js',
}); })

View file

@ -1 +1 @@
export default defineNitroConfig({ extends: "@mcflyjs/config" }); export default defineNitroConfig({ extends: '@mcflyjs/config' })

View file

@ -1,3 +1,118 @@
/* PrismJS 1.29.0 /* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help} code[class*='language-'],
pre[class*='language-'] {
color: #000;
background: 0 0;
text-shadow: 0 1px #fff;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
code[class*='language-'] ::-moz-selection,
code[class*='language-']::-moz-selection,
pre[class*='language-'] ::-moz-selection,
pre[class*='language-']::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
code[class*='language-'] ::selection,
code[class*='language-']::selection,
pre[class*='language-'] ::selection,
pre[class*='language-']::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*='language-'],
pre[class*='language-'] {
text-shadow: none;
}
}
pre[class*='language-'] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
}
:not(pre) > code[class*='language-'],
pre[class*='language-'] {
background: #f5f2f0;
}
:not(pre) > code[class*='language-'] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.cdata,
.token.comment,
.token.doctype,
.token.prolog {
color: #708090;
}
.token.punctuation {
color: #999;
}
.token.namespace {
opacity: 0.7;
}
.token.boolean,
.token.constant,
.token.deleted,
.token.number,
.token.property,
.token.symbol,
.token.tag {
color: #905;
}
.token.attr-name,
.token.builtin,
.token.char,
.token.inserted,
.token.selector,
.token.string {
color: #690;
}
.language-css .token.string,
.style .token.string,
.token.entity,
.token.operator,
.token.url {
color: #9a6e3a;
background: hsla(0, 0%, 100%, 0.5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.class-name,
.token.function {
color: #dd4a68;
}
.token.important,
.token.regex,
.token.variable {
color: #e90;
}
.token.bold,
.token.important {
font-weight: 700;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View file

@ -1,9 +1,9 @@
/* PrismJS 1.29.0 /* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
var _self = var _self =
"undefined" != typeof window 'undefined' != typeof window
? window ? window
: "undefined" != typeof WorkerGlobalScope && : 'undefined' != typeof WorkerGlobalScope &&
self instanceof WorkerGlobalScope self instanceof WorkerGlobalScope
? self ? self
: {}, : {},
@ -22,27 +22,27 @@ var _self =
: Array.isArray(n) : Array.isArray(n)
? n.map(e) ? n.map(e)
: n : n
.replace(/&/g, "&amp;") .replace(/&/g, '&amp;')
.replace(/</g, "&lt;") .replace(/</g, '&lt;')
.replace(/\u00a0/g, " "); .replace(/\u00a0/g, ' ')
}, },
type: function (e) { type: function (e) {
return Object.prototype.toString.call(e).slice(8, -1); return Object.prototype.toString.call(e).slice(8, -1)
}, },
objId: function (e) { objId: function (e) {
return ( return (
e.__id || Object.defineProperty(e, "__id", { value: ++t }), e.__id e.__id || Object.defineProperty(e, '__id', { value: ++t }), e.__id
); )
}, },
clone: function e(n, t) { clone: function e(n, t) {
var r, i; var r, i
switch (((t = t || {}), a.util.type(n))) { switch (((t = t || {}), a.util.type(n))) {
case "Object": case 'Object':
if (((i = a.util.objId(n)), t[i])) return t[i]; if (((i = a.util.objId(n)), t[i])) return t[i]
for (var l in ((r = {}), (t[i] = r), n)) for (var l in ((r = {}), (t[i] = r), n))
n.hasOwnProperty(l) && (r[l] = e(n[l], t)); n.hasOwnProperty(l) && (r[l] = e(n[l], t))
return r; return r
case "Array": case 'Array':
return ( return (
(i = a.util.objId(n)), (i = a.util.objId(n)),
t[i] t[i]
@ -50,49 +50,49 @@ var _self =
: ((r = []), : ((r = []),
(t[i] = r), (t[i] = r),
n.forEach(function (n, a) { n.forEach(function (n, a) {
r[a] = e(n, t); r[a] = e(n, t)
}), }),
r) r)
); )
default: default:
return n; return n
} }
}, },
getLanguage: function (e) { getLanguage: function (e) {
for (; e; ) { for (; e; ) {
var t = n.exec(e.className); var t = n.exec(e.className)
if (t) return t[1].toLowerCase(); if (t) return t[1].toLowerCase()
e = e.parentElement; e = e.parentElement
} }
return "none"; return 'none'
}, },
setLanguage: function (e, t) { setLanguage: function (e, t) {
(e.className = e.className.replace(RegExp(n, "gi"), "")), ;(e.className = e.className.replace(RegExp(n, 'gi'), '')),
e.classList.add("language-" + t); e.classList.add('language-' + t)
}, },
currentScript: function () { currentScript: function () {
if ("undefined" == typeof document) return null; if ('undefined' == typeof document) return null
if ("currentScript" in document) return document.currentScript; if ('currentScript' in document) return document.currentScript
try { try {
throw new Error(); throw new Error()
} catch (r) { } catch (r) {
var e = (/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(r.stack) || var e = (/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(r.stack) ||
[])[1]; [])[1]
if (e) { if (e) {
var n = document.getElementsByTagName("script"); var n = document.getElementsByTagName('script')
for (var t in n) if (n[t].src == e) return n[t]; for (var t in n) if (n[t].src == e) return n[t]
} }
return null; return null
} }
}, },
isActive: function (e, n, t) { isActive: function (e, n, t) {
for (var r = "no-" + n; e; ) { for (var r = 'no-' + n; e; ) {
var a = e.classList; var a = e.classList
if (a.contains(n)) return !0; if (a.contains(n)) return !0
if (a.contains(r)) return !1; if (a.contains(r)) return !1
e = e.parentElement; e = e.parentElement
} }
return !!t; return !!t
}, },
}, },
languages: { languages: {
@ -101,45 +101,45 @@ var _self =
text: r, text: r,
txt: r, txt: r,
extend: function (e, n) { extend: function (e, n) {
var t = a.util.clone(a.languages[e]); var t = a.util.clone(a.languages[e])
for (var r in n) t[r] = n[r]; for (var r in n) t[r] = n[r]
return t; return t
}, },
insertBefore: function (e, n, t, r) { insertBefore: function (e, n, t, r) {
var i = (r = r || a.languages)[e], var i = (r = r || a.languages)[e],
l = {}; l = {}
for (var o in i) for (var o in i)
if (i.hasOwnProperty(o)) { if (i.hasOwnProperty(o)) {
if (o == n) if (o == n)
for (var s in t) t.hasOwnProperty(s) && (l[s] = t[s]); for (var s in t) t.hasOwnProperty(s) && (l[s] = t[s])
t.hasOwnProperty(o) || (l[o] = i[o]); t.hasOwnProperty(o) || (l[o] = i[o])
} }
var u = r[e]; var u = r[e]
return ( return (
(r[e] = l), (r[e] = l),
a.languages.DFS(a.languages, function (n, t) { a.languages.DFS(a.languages, function (n, t) {
t === u && n != e && (this[n] = l); t === u && n != e && (this[n] = l)
}), }),
l l
); )
}, },
DFS: function e(n, t, r, i) { DFS: function e(n, t, r, i) {
i = i || {}; i = i || {}
var l = a.util.objId; var l = a.util.objId
for (var o in n) for (var o in n)
if (n.hasOwnProperty(o)) { if (n.hasOwnProperty(o)) {
t.call(n, o, n[o], r || o); t.call(n, o, n[o], r || o)
var s = n[o], var s = n[o],
u = a.util.type(s); u = a.util.type(s)
"Object" !== u || i[l(s)] 'Object' !== u || i[l(s)]
? "Array" !== u || i[l(s)] || ((i[l(s)] = !0), e(s, t, o, i)) ? 'Array' !== u || i[l(s)] || ((i[l(s)] = !0), e(s, t, o, i))
: ((i[l(s)] = !0), e(s, t, null, i)); : ((i[l(s)] = !0), e(s, t, null, i))
} }
}, },
}, },
plugins: {}, plugins: {},
highlightAll: function (e, n) { highlightAll: function (e, n) {
a.highlightAllUnder(document, e, n); a.highlightAllUnder(document, e, n)
}, },
highlightAllUnder: function (e, n, t) { highlightAllUnder: function (e, n, t) {
var r = { var r = {
@ -147,162 +147,160 @@ var _self =
container: e, container: e,
selector: selector:
'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code', 'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code',
}; }
a.hooks.run("before-highlightall", r), a.hooks.run('before-highlightall', r),
(r.elements = Array.prototype.slice.apply( (r.elements = Array.prototype.slice.apply(
r.container.querySelectorAll(r.selector), r.container.querySelectorAll(r.selector)
)), )),
a.hooks.run("before-all-elements-highlight", r); a.hooks.run('before-all-elements-highlight', r)
for (var i, l = 0; (i = r.elements[l++]); ) for (var i, l = 0; (i = r.elements[l++]); )
a.highlightElement(i, !0 === n, r.callback); a.highlightElement(i, !0 === n, r.callback)
}, },
highlightElement: function (n, t, r) { highlightElement: function (n, t, r) {
var i = a.util.getLanguage(n), var i = a.util.getLanguage(n),
l = a.languages[i]; l = a.languages[i]
a.util.setLanguage(n, i); a.util.setLanguage(n, i)
var o = n.parentElement; var o = n.parentElement
o && "pre" === o.nodeName.toLowerCase() && a.util.setLanguage(o, i); o && 'pre' === o.nodeName.toLowerCase() && a.util.setLanguage(o, i)
var s = { element: n, language: i, grammar: l, code: n.textContent }; var s = { element: n, language: i, grammar: l, code: n.textContent }
function u(e) { function u(e) {
(s.highlightedCode = e), ;(s.highlightedCode = e),
a.hooks.run("before-insert", s), a.hooks.run('before-insert', s),
(s.element.innerHTML = s.highlightedCode), (s.element.innerHTML = s.highlightedCode),
a.hooks.run("after-highlight", s), a.hooks.run('after-highlight', s),
a.hooks.run("complete", s), a.hooks.run('complete', s),
r && r.call(s.element); r && r.call(s.element)
} }
if ( if (
(a.hooks.run("before-sanity-check", s), (a.hooks.run('before-sanity-check', s),
(o = s.element.parentElement) && (o = s.element.parentElement) &&
"pre" === o.nodeName.toLowerCase() && 'pre' === o.nodeName.toLowerCase() &&
!o.hasAttribute("tabindex") && !o.hasAttribute('tabindex') &&
o.setAttribute("tabindex", "0"), o.setAttribute('tabindex', '0'),
!s.code) !s.code)
) )
return a.hooks.run("complete", s), void (r && r.call(s.element)); return a.hooks.run('complete', s), void (r && r.call(s.element))
if ((a.hooks.run("before-highlight", s), s.grammar)) if ((a.hooks.run('before-highlight', s), s.grammar))
if (t && e.Worker) { if (t && e.Worker) {
var c = new Worker(a.filename); var c = new Worker(a.filename)
(c.onmessage = function (e) { ;(c.onmessage = function (e) {
u(e.data); u(e.data)
}), }),
c.postMessage( c.postMessage(
JSON.stringify({ JSON.stringify({
language: s.language, language: s.language,
code: s.code, code: s.code,
immediateClose: !0, immediateClose: !0,
}), })
); )
} else u(a.highlight(s.code, s.grammar, s.language)); } else u(a.highlight(s.code, s.grammar, s.language))
else u(a.util.encode(s.code)); else u(a.util.encode(s.code))
}, },
highlight: function (e, n, t) { highlight: function (e, n, t) {
var r = { code: e, grammar: n, language: t }; var r = { code: e, grammar: n, language: t }
if ((a.hooks.run("before-tokenize", r), !r.grammar)) if ((a.hooks.run('before-tokenize', r), !r.grammar))
throw new Error( throw new Error('The language "' + r.language + '" has no grammar.')
'The language "' + r.language + '" has no grammar.',
);
return ( return (
(r.tokens = a.tokenize(r.code, r.grammar)), (r.tokens = a.tokenize(r.code, r.grammar)),
a.hooks.run("after-tokenize", r), a.hooks.run('after-tokenize', r),
i.stringify(a.util.encode(r.tokens), r.language) i.stringify(a.util.encode(r.tokens), r.language)
); )
}, },
tokenize: function (e, n) { tokenize: function (e, n) {
var t = n.rest; var t = n.rest
if (t) { if (t) {
for (var r in t) n[r] = t[r]; for (var r in t) n[r] = t[r]
delete n.rest; delete n.rest
} }
var a = new s(); var a = new s()
return ( return (
u(a, a.head, e), u(a, a.head, e),
o(e, a, n, a.head, 0), o(e, a, n, a.head, 0),
(function (e) { (function (e) {
for (var n = [], t = e.head.next; t !== e.tail; ) for (var n = [], t = e.head.next; t !== e.tail; )
n.push(t.value), (t = t.next); n.push(t.value), (t = t.next)
return n; return n
})(a) })(a)
); )
}, },
hooks: { hooks: {
all: {}, all: {},
add: function (e, n) { add: function (e, n) {
var t = a.hooks.all; var t = a.hooks.all
(t[e] = t[e] || []), t[e].push(n); ;(t[e] = t[e] || []), t[e].push(n)
}, },
run: function (e, n) { run: function (e, n) {
var t = a.hooks.all[e]; var t = a.hooks.all[e]
if (t && t.length) for (var r, i = 0; (r = t[i++]); ) r(n); if (t && t.length) for (var r, i = 0; (r = t[i++]); ) r(n)
}, },
}, },
Token: i, Token: i,
}; }
function i(e, n, t, r) { function i(e, n, t, r) {
(this.type = e), ;(this.type = e),
(this.content = n), (this.content = n),
(this.alias = t), (this.alias = t),
(this.length = 0 | (r || "").length); (this.length = 0 | (r || '').length)
} }
function l(e, n, t, r) { function l(e, n, t, r) {
e.lastIndex = n; e.lastIndex = n
var a = e.exec(t); var a = e.exec(t)
if (a && r && a[1]) { if (a && r && a[1]) {
var i = a[1].length; var i = a[1].length
(a.index += i), (a[0] = a[0].slice(i)); ;(a.index += i), (a[0] = a[0].slice(i))
} }
return a; return a
} }
function o(e, n, t, r, s, g) { function o(e, n, t, r, s, g) {
for (var f in t) for (var f in t)
if (t.hasOwnProperty(f) && t[f]) { if (t.hasOwnProperty(f) && t[f]) {
var h = t[f]; var h = t[f]
h = Array.isArray(h) ? h : [h]; h = Array.isArray(h) ? h : [h]
for (var d = 0; d < h.length; ++d) { for (var d = 0; d < h.length; ++d) {
if (g && g.cause == f + "," + d) return; if (g && g.cause == f + ',' + d) return
var v = h[d], var v = h[d],
p = v.inside, p = v.inside,
m = !!v.lookbehind, m = !!v.lookbehind,
y = !!v.greedy, y = !!v.greedy,
k = v.alias; k = v.alias
if (y && !v.pattern.global) { if (y && !v.pattern.global) {
var x = v.pattern.toString().match(/[imsuy]*$/)[0]; var x = v.pattern.toString().match(/[imsuy]*$/)[0]
v.pattern = RegExp(v.pattern.source, x + "g"); v.pattern = RegExp(v.pattern.source, x + 'g')
} }
for ( for (
var b = v.pattern || v, w = r.next, A = s; var b = v.pattern || v, w = r.next, A = s;
w !== n.tail && !(g && A >= g.reach); w !== n.tail && !(g && A >= g.reach);
A += w.value.length, w = w.next A += w.value.length, w = w.next
) { ) {
var E = w.value; var E = w.value
if (n.length > e.length) return; if (n.length > e.length) return
if (!(E instanceof i)) { if (!(E instanceof i)) {
var P, var P,
L = 1; L = 1
if (y) { if (y) {
if (!(P = l(b, A, e, m)) || P.index >= e.length) break; if (!(P = l(b, A, e, m)) || P.index >= e.length) break
var S = P.index, var S = P.index,
O = P.index + P[0].length, O = P.index + P[0].length,
j = A; j = A
for (j += w.value.length; S >= j; ) for (j += w.value.length; S >= j; )
j += (w = w.next).value.length; j += (w = w.next).value.length
if (((A = j -= w.value.length), w.value instanceof i)) if (((A = j -= w.value.length), w.value instanceof i))
continue; continue
for ( for (
var C = w; var C = w;
C !== n.tail && (j < O || "string" == typeof C.value); C !== n.tail && (j < O || 'string' == typeof C.value);
C = C.next C = C.next
) )
L++, (j += C.value.length); L++, (j += C.value.length)
L--, (E = e.slice(A, j)), (P.index -= A); L--, (E = e.slice(A, j)), (P.index -= A)
} else if (!(P = l(b, 0, E, m))) continue; } else if (!(P = l(b, 0, E, m))) continue
S = P.index; S = P.index
var N = P[0], var N = P[0],
_ = E.slice(0, S), _ = E.slice(0, S),
M = E.slice(S + N.length), M = E.slice(S + N.length),
W = A + E.length; W = A + E.length
g && W > g.reach && (g.reach = W); g && W > g.reach && (g.reach = W)
var z = w.prev; var z = w.prev
if ( if (
(_ && ((z = u(n, z, _)), (A += _.length)), (_ && ((z = u(n, z, _)), (A += _.length)),
c(n, z, L), c(n, z, L),
@ -310,9 +308,9 @@ var _self =
M && u(n, w, M), M && u(n, w, M),
L > 1) L > 1)
) { ) {
var I = { cause: f + "," + d, reach: W }; var I = { cause: f + ',' + d, reach: W }
o(e, n, t, w.prev, A, I), o(e, n, t, w.prev, A, I),
g && I.reach > g.reach && (g.reach = I.reach); g && I.reach > g.reach && (g.reach = I.reach)
} }
} }
} }
@ -321,107 +319,106 @@ var _self =
} }
function s() { function s() {
var e = { value: null, prev: null, next: null }, var e = { value: null, prev: null, next: null },
n = { value: null, prev: e, next: null }; n = { value: null, prev: e, next: null }
(e.next = n), (this.head = e), (this.tail = n), (this.length = 0); ;(e.next = n), (this.head = e), (this.tail = n), (this.length = 0)
} }
function u(e, n, t) { function u(e, n, t) {
var r = n.next, var r = n.next,
a = { value: t, prev: n, next: r }; a = { value: t, prev: n, next: r }
return (n.next = a), (r.prev = a), e.length++, a; return (n.next = a), (r.prev = a), e.length++, a
} }
function c(e, n, t) { function c(e, n, t) {
for (var r = n.next, a = 0; a < t && r !== e.tail; a++) r = r.next; for (var r = n.next, a = 0; a < t && r !== e.tail; a++) r = r.next
(n.next = r), (r.prev = n), (e.length -= a); ;(n.next = r), (r.prev = n), (e.length -= a)
} }
if ( if (
((e.Prism = a), ((e.Prism = a),
(i.stringify = function e(n, t) { (i.stringify = function e(n, t) {
if ("string" == typeof n) return n; if ('string' == typeof n) return n
if (Array.isArray(n)) { if (Array.isArray(n)) {
var r = ""; var r = ''
return ( return (
n.forEach(function (n) { n.forEach(function (n) {
r += e(n, t); r += e(n, t)
}), }),
r r
); )
} }
var i = { var i = {
type: n.type, type: n.type,
content: e(n.content, t), content: e(n.content, t),
tag: "span", tag: 'span',
classes: ["token", n.type], classes: ['token', n.type],
attributes: {}, attributes: {},
language: t, language: t,
}, },
l = n.alias; l = n.alias
l && l &&
(Array.isArray(l) (Array.isArray(l)
? Array.prototype.push.apply(i.classes, l) ? Array.prototype.push.apply(i.classes, l)
: i.classes.push(l)), : i.classes.push(l)),
a.hooks.run("wrap", i); a.hooks.run('wrap', i)
var o = ""; var o = ''
for (var s in i.attributes) for (var s in i.attributes)
o += o +=
" " + ' ' +
s + s +
'="' + '="' +
(i.attributes[s] || "").replace(/"/g, "&quot;") + (i.attributes[s] || '').replace(/"/g, '&quot;') +
'"'; '"'
return ( return (
"<" + '<' +
i.tag + i.tag +
' class="' + ' class="' +
i.classes.join(" ") + i.classes.join(' ') +
'"' + '"' +
o + o +
">" + '>' +
i.content + i.content +
"</" + '</' +
i.tag + i.tag +
">" '>'
); )
}), }),
!e.document) !e.document)
) )
return e.addEventListener return e.addEventListener
? (a.disableWorkerMessageHandler || ? (a.disableWorkerMessageHandler ||
e.addEventListener( e.addEventListener(
"message", 'message',
function (n) { function (n) {
var t = JSON.parse(n.data), var t = JSON.parse(n.data),
r = t.language, r = t.language,
i = t.code, i = t.code,
l = t.immediateClose; l = t.immediateClose
e.postMessage(a.highlight(i, a.languages[r], r)), e.postMessage(a.highlight(i, a.languages[r], r)), l && e.close()
l && e.close();
}, },
!1, !1
), ),
a) a)
: a; : a
var g = a.util.currentScript(); var g = a.util.currentScript()
function f() { function f() {
a.manual || a.highlightAll(); a.manual || a.highlightAll()
} }
if ( if (
(g && (g &&
((a.filename = g.src), ((a.filename = g.src),
g.hasAttribute("data-manual") && (a.manual = !0)), g.hasAttribute('data-manual') && (a.manual = !0)),
!a.manual) !a.manual)
) { ) {
var h = document.readyState; var h = document.readyState
"loading" === h || ("interactive" === h && g && g.defer) 'loading' === h || ('interactive' === h && g && g.defer)
? document.addEventListener("DOMContentLoaded", f) ? document.addEventListener('DOMContentLoaded', f)
: window.requestAnimationFrame : window.requestAnimationFrame
? window.requestAnimationFrame(f) ? window.requestAnimationFrame(f)
: window.setTimeout(f, 16); : window.setTimeout(f, 16)
} }
return a; return a
})(_self); })(_self)
"undefined" != typeof module && module.exports && (module.exports = Prism), 'undefined' != typeof module && module.exports && (module.exports = Prism),
"undefined" != typeof global && (global.Prism = Prism); 'undefined' != typeof global && (global.Prism = Prism)
(Prism.languages.markup = { ;(Prism.languages.markup = {
comment: { pattern: /<!--(?:(?!<!--)[\s\S])*?-->/, greedy: !0 }, comment: { pattern: /<!--(?:(?!<!--)[\s\S])*?-->/, greedy: !0 },
prolog: { pattern: /<\?[\s\S]+?\?>/, greedy: !0 }, prolog: { pattern: /<\?[\s\S]+?\?>/, greedy: !0 },
doctype: { doctype: {
@ -429,7 +426,7 @@ var _self =
/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i, /<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,
greedy: !0, greedy: !0,
inside: { inside: {
"internal-subset": { 'internal-subset': {
pattern: /(^[^\[]*\[)[\s\S]+(?=\]>$)/, pattern: /(^[^\[]*\[)[\s\S]+(?=\]>$)/,
lookbehind: !0, lookbehind: !0,
greedy: !0, greedy: !0,
@ -437,7 +434,7 @@ var _self =
}, },
string: { pattern: /"[^"]*"|'[^']*'/, greedy: !0 }, string: { pattern: /"[^"]*"|'[^']*'/, greedy: !0 },
punctuation: /^<!|>$|[[\]]/, punctuation: /^<!|>$|[[\]]/,
"doctype-tag": /^DOCTYPE/i, 'doctype-tag': /^DOCTYPE/i,
name: /[^\s<>'"]+/, name: /[^\s<>'"]+/,
}, },
}, },
@ -451,120 +448,118 @@ var _self =
pattern: /^<\/?[^\s>\/]+/, pattern: /^<\/?[^\s>\/]+/,
inside: { punctuation: /^<\/?/, namespace: /^[^\s>\/:]+:/ }, inside: { punctuation: /^<\/?/, namespace: /^[^\s>\/:]+:/ },
}, },
"special-attr": [], 'special-attr': [],
"attr-value": { 'attr-value': {
pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/, pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,
inside: { inside: {
punctuation: [ punctuation: [
{ pattern: /^=/, alias: "attr-equals" }, { pattern: /^=/, alias: 'attr-equals' },
{ pattern: /^(\s*)["']|["']$/, lookbehind: !0 }, { pattern: /^(\s*)["']|["']$/, lookbehind: !0 },
], ],
}, },
}, },
punctuation: /\/?>/, punctuation: /\/?>/,
"attr-name": { 'attr-name': {
pattern: /[^\s>\/]+/, pattern: /[^\s>\/]+/,
inside: { namespace: /^[^\s>\/:]+:/ }, inside: { namespace: /^[^\s>\/:]+:/ },
}, },
}, },
}, },
entity: [ entity: [
{ pattern: /&[\da-z]{1,8};/i, alias: "named-entity" }, { pattern: /&[\da-z]{1,8};/i, alias: 'named-entity' },
/&#x?[\da-f]{1,8};/i, /&#x?[\da-f]{1,8};/i,
], ],
}), }),
(Prism.languages.markup.tag.inside["attr-value"].inside.entity = (Prism.languages.markup.tag.inside['attr-value'].inside.entity =
Prism.languages.markup.entity), Prism.languages.markup.entity),
(Prism.languages.markup.doctype.inside["internal-subset"].inside = (Prism.languages.markup.doctype.inside['internal-subset'].inside =
Prism.languages.markup), Prism.languages.markup),
Prism.hooks.add("wrap", function (a) { Prism.hooks.add('wrap', function (a) {
"entity" === a.type && 'entity' === a.type &&
(a.attributes.title = a.content.replace(/&amp;/, "&")); (a.attributes.title = a.content.replace(/&amp;/, '&'))
}), }),
Object.defineProperty(Prism.languages.markup.tag, "addInlined", { Object.defineProperty(Prism.languages.markup.tag, 'addInlined', {
value: function (a, e) { value: function (a, e) {
var s = {}; var s = {}
(s["language-" + e] = { ;(s['language-' + e] = {
pattern: /(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i, pattern: /(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,
lookbehind: !0, lookbehind: !0,
inside: Prism.languages[e], inside: Prism.languages[e],
}), }),
(s.cdata = /^<!\[CDATA\[|\]\]>$/i); (s.cdata = /^<!\[CDATA\[|\]\]>$/i)
var t = { var t = {
"included-cdata": { pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i, inside: s }, 'included-cdata': { pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i, inside: s },
}; }
t["language-" + e] = { pattern: /[\s\S]+/, inside: Prism.languages[e] }; t['language-' + e] = { pattern: /[\s\S]+/, inside: Prism.languages[e] }
var n = {}; var n = {}
(n[a] = { ;(n[a] = {
pattern: RegExp( pattern: RegExp(
"(<__[^>]*>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)".replace( '(<__[^>]*>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)'.replace(
/__/g, /__/g,
function () { function () {
return a; return a
}, }
), ),
"i", 'i'
), ),
lookbehind: !0, lookbehind: !0,
greedy: !0, greedy: !0,
inside: t, inside: t,
}), }),
Prism.languages.insertBefore("markup", "cdata", n); Prism.languages.insertBefore('markup', 'cdata', n)
}, },
}), }),
Object.defineProperty(Prism.languages.markup.tag, "addAttribute", { Object.defineProperty(Prism.languages.markup.tag, 'addAttribute', {
value: function (a, e) { value: function (a, e) {
Prism.languages.markup.tag.inside["special-attr"].push({ Prism.languages.markup.tag.inside['special-attr'].push({
pattern: RegExp( pattern: RegExp(
"(^|[\"'\\s])(?:" + '(^|["\'\\s])(?:' +
a + a +
")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))", ')\\s*=\\s*(?:"[^"]*"|\'[^\']*\'|[^\\s\'">=]+(?=[\\s>]))',
"i", 'i'
), ),
lookbehind: !0, lookbehind: !0,
inside: { inside: {
"attr-name": /^[^\s=]+/, 'attr-name': /^[^\s=]+/,
"attr-value": { 'attr-value': {
pattern: /=[\s\S]+/, pattern: /=[\s\S]+/,
inside: { inside: {
value: { value: {
pattern: /(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/, pattern: /(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,
lookbehind: !0, lookbehind: !0,
alias: [e, "language-" + e], alias: [e, 'language-' + e],
inside: Prism.languages[e], inside: Prism.languages[e],
}, },
punctuation: [{ pattern: /^=/, alias: "attr-equals" }, /"|'/], punctuation: [{ pattern: /^=/, alias: 'attr-equals' }, /"|'/],
}, },
}, },
}, },
}); })
}, },
}), }),
(Prism.languages.html = Prism.languages.markup), (Prism.languages.html = Prism.languages.markup),
(Prism.languages.mathml = Prism.languages.markup), (Prism.languages.mathml = Prism.languages.markup),
(Prism.languages.svg = Prism.languages.markup), (Prism.languages.svg = Prism.languages.markup),
(Prism.languages.xml = Prism.languages.extend("markup", {})), (Prism.languages.xml = Prism.languages.extend('markup', {})),
(Prism.languages.ssml = Prism.languages.xml), (Prism.languages.ssml = Prism.languages.xml),
(Prism.languages.atom = Prism.languages.xml), (Prism.languages.atom = Prism.languages.xml),
(Prism.languages.rss = Prism.languages.xml); (Prism.languages.rss = Prism.languages.xml)
!(function (s) { !(function (s) {
var e = var e =
/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/; /(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/
(s.languages.css = { ;(s.languages.css = {
comment: /\/\*[\s\S]*?\*\//, comment: /\/\*[\s\S]*?\*\//,
atrule: { atrule: {
pattern: RegExp( pattern: RegExp(
"@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|" + '@[\\w-](?:[^;{\\s"\']|\\s+(?!\\s)|' + e.source + ')*?(?:;|(?=\\s*\\{))'
e.source +
")*?(?:;|(?=\\s*\\{))",
), ),
inside: { inside: {
rule: /^@[\w-]+/, rule: /^@[\w-]+/,
"selector-function-argument": { 'selector-function-argument': {
pattern: pattern:
/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/, /(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,
lookbehind: !0, lookbehind: !0,
alias: "selector", alias: 'selector',
}, },
keyword: { keyword: {
pattern: /(^|[^\w-])(?:and|not|only|or)(?![\w-])/, pattern: /(^|[^\w-])(?:and|not|only|or)(?![\w-])/,
@ -574,21 +569,21 @@ var _self =
}, },
url: { url: {
pattern: RegExp( pattern: RegExp(
"\\burl\\((?:" + e.source + "|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)", '\\burl\\((?:' + e.source + '|(?:[^\\\\\r\n()"\']|\\\\[^])*)\\)',
"i", 'i'
), ),
greedy: !0, greedy: !0,
inside: { inside: {
function: /^url/i, function: /^url/i,
punctuation: /^\(|\)$/, punctuation: /^\(|\)$/,
string: { pattern: RegExp("^" + e.source + "$"), alias: "url" }, string: { pattern: RegExp('^' + e.source + '$'), alias: 'url' },
}, },
}, },
selector: { selector: {
pattern: RegExp( pattern: RegExp(
"(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|" + '(^|[{}\\s])[^{}\\s](?:[^{};"\'\\s]|\\s+(?![\\s{])|' +
e.source + e.source +
")*(?=\\s*\\{)", ')*(?=\\s*\\{)'
), ),
lookbehind: !0, lookbehind: !0,
}, },
@ -602,10 +597,10 @@ var _self =
function: { pattern: /(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i, lookbehind: !0 }, function: { pattern: /(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i, lookbehind: !0 },
punctuation: /[(){};:,]/, punctuation: /[(){};:,]/,
}), }),
(s.languages.css.atrule.inside.rest = s.languages.css); (s.languages.css.atrule.inside.rest = s.languages.css)
var t = s.languages.markup; var t = s.languages.markup
t && (t.tag.addInlined("style", "css"), t.tag.addAttribute("style", "css")); t && (t.tag.addInlined('style', 'css'), t.tag.addAttribute('style', 'css'))
})(Prism); })(Prism)
Prism.languages.clike = { Prism.languages.clike = {
comment: [ comment: [
{ pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/, lookbehind: !0, greedy: !0 }, { pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/, lookbehind: !0, greedy: !0 },
@ -615,7 +610,7 @@ Prism.languages.clike = {
pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
greedy: !0, greedy: !0,
}, },
"class-name": { 'class-name': {
pattern: pattern:
/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i, /(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,
lookbehind: !0, lookbehind: !0,
@ -628,10 +623,10 @@ Prism.languages.clike = {
number: /\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i, number: /\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,
operator: /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/, operator: /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,
punctuation: /[{}[\];(),.:]/, punctuation: /[{}[\];(),.:]/,
}; }
(Prism.languages.javascript = Prism.languages.extend("clike", { ;(Prism.languages.javascript = Prism.languages.extend('clike', {
"class-name": [ 'class-name': [
Prism.languages.clike["class-name"], Prism.languages.clike['class-name'],
{ {
pattern: pattern:
/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/, /(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,
@ -650,37 +645,37 @@ Prism.languages.clike = {
/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/, /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,
number: { number: {
pattern: RegExp( pattern: RegExp(
"(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])", '(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])'
), ),
lookbehind: !0, lookbehind: !0,
}, },
operator: operator:
/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/, /--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/,
})), })),
(Prism.languages.javascript["class-name"][0].pattern = (Prism.languages.javascript['class-name'][0].pattern =
/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/), /(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/),
Prism.languages.insertBefore("javascript", "keyword", { Prism.languages.insertBefore('javascript', 'keyword', {
regex: { regex: {
pattern: RegExp( pattern: RegExp(
"((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))", '((?:^|[^$\\w\\xA0-\\uFFFF."\'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))'
), ),
lookbehind: !0, lookbehind: !0,
greedy: !0, greedy: !0,
inside: { inside: {
"regex-source": { 'regex-source': {
pattern: /^(\/)[\s\S]+(?=\/[a-z]*$)/, pattern: /^(\/)[\s\S]+(?=\/[a-z]*$)/,
lookbehind: !0, lookbehind: !0,
alias: "language-regex", alias: 'language-regex',
inside: Prism.languages.regex, inside: Prism.languages.regex,
}, },
"regex-delimiter": /^\/|\/$/, 'regex-delimiter': /^\/|\/$/,
"regex-flags": /^[a-z]+$/, 'regex-flags': /^[a-z]+$/,
}, },
}, },
"function-variable": { 'function-variable': {
pattern: pattern:
/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/, /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,
alias: "function", alias: 'function',
}, },
parameter: [ parameter: [
{ {
@ -710,22 +705,22 @@ Prism.languages.clike = {
], ],
constant: /\b[A-Z](?:[A-Z_]|\dx?)*\b/, constant: /\b[A-Z](?:[A-Z_]|\dx?)*\b/,
}), }),
Prism.languages.insertBefore("javascript", "string", { Prism.languages.insertBefore('javascript', 'string', {
hashbang: { pattern: /^#!.*/, greedy: !0, alias: "comment" }, hashbang: { pattern: /^#!.*/, greedy: !0, alias: 'comment' },
"template-string": { 'template-string': {
pattern: pattern:
/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/, /`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,
greedy: !0, greedy: !0,
inside: { inside: {
"template-punctuation": { pattern: /^`|`$/, alias: "string" }, 'template-punctuation': { pattern: /^`|`$/, alias: 'string' },
interpolation: { interpolation: {
pattern: pattern:
/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/, /((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,
lookbehind: !0, lookbehind: !0,
inside: { inside: {
"interpolation-punctuation": { 'interpolation-punctuation': {
pattern: /^\$\{|\}$/, pattern: /^\$\{|\}$/,
alias: "punctuation", alias: 'punctuation',
}, },
rest: Prism.languages.javascript, rest: Prism.languages.javascript,
}, },
@ -733,26 +728,26 @@ Prism.languages.clike = {
string: /[\s\S]+/, string: /[\s\S]+/,
}, },
}, },
"string-property": { 'string-property': {
pattern: pattern:
/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m, /((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,
lookbehind: !0, lookbehind: !0,
greedy: !0, greedy: !0,
alias: "property", alias: 'property',
}, },
}), }),
Prism.languages.insertBefore("javascript", "operator", { Prism.languages.insertBefore('javascript', 'operator', {
"literal-property": { 'literal-property': {
pattern: pattern:
/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m, /((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,
lookbehind: !0, lookbehind: !0,
alias: "property", alias: 'property',
}, },
}), }),
Prism.languages.markup && Prism.languages.markup &&
(Prism.languages.markup.tag.addInlined("script", "javascript"), (Prism.languages.markup.tag.addInlined('script', 'javascript'),
Prism.languages.markup.tag.addAttribute( Prism.languages.markup.tag.addAttribute(
"on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)", 'on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)',
"javascript", 'javascript'
)), )),
(Prism.languages.js = Prism.languages.javascript); (Prism.languages.js = Prism.languages.javascript)

View file

@ -5,5 +5,5 @@
* ...reusable code are in ./src/components * ...reusable code are in ./src/components
* @see https://ayco.io/gh/McFly#special-directories * @see https://ayco.io/gh/McFly#special-directories
*/ */
import config from "../mcfly.config.mjs"; import config from '../mcfly.config.mjs'
export default useMcFlyRoute({ config, storage: useStorage() }); export default useMcFlyRoute({ config, storage: useStorage() })

View file

@ -1,41 +1,41 @@
class CodeBlockComponent extends HTMLElement { class CodeBlockComponent extends HTMLElement {
connectedCallback() { connectedCallback() {
const trimmed = this.innerHTML.trim(); const trimmed = this.innerHTML.trim()
const lang = this.getAttribute("language"); const lang = this.getAttribute('language')
const inline = this.getAttribute("inline") !== null; const inline = this.getAttribute('inline') !== null
this.innerHTML = ` this.innerHTML = `
<pre><code id="code">${trimmed}</code></pre> <pre><code id="code">${trimmed}</code></pre>
`; `
/** /**
* @type {HTMLPreElement} * @type {HTMLPreElement}
*/ */
const pre = this.querySelector("pre"); const pre = this.querySelector('pre')
if (lang) { if (lang) {
pre.className = `language-${lang}`; pre.className = `language-${lang}`
} }
/** /**
* @type {Partial<CSSStyleDeclaration>} * @type {Partial<CSSStyleDeclaration>}
*/ */
const style = { const style = {
background: "#f5f2f0", background: '#f5f2f0',
padding: "1em", padding: '1em',
margin: "1em 0", margin: '1em 0',
fontSize: "large", fontSize: 'large',
overflow: "auto", overflow: 'auto',
borderRadius: "5px", borderRadius: '5px',
}; }
if (inline) { if (inline) {
style.display = "inline"; style.display = 'inline'
style.padding = "0.25em 0.3em"; style.padding = '0.25em 0.3em'
} }
Object.keys(style).forEach((rule) => { Object.keys(style).forEach((rule) => {
pre.style[rule] = style[rule]; pre.style[rule] = style[rule]
}); })
} }
} }

View file

@ -1,44 +1,44 @@
class FeatureSet extends WebComponent { class FeatureSet extends WebComponent {
#features = [ #features = [
{ {
icon: "️🔄", icon: '️🔄',
title: "Reactive.", title: 'Reactive.',
description: description:
"A robust API for synchronizing your component's UI and properties", "A robust API for synchronizing your component's UI and properties",
}, },
{ {
icon: "️🤏", icon: '️🤏',
title: "Tiny.", title: 'Tiny.',
description: description:
"~1 kB base class (minified, compressed) with versatile utilities", '~1 kB base class (minified, compressed) with versatile utilities',
}, },
{ {
icon: "😌", icon: '😌',
title: "Easy.", title: 'Easy.',
description: "Sensible life-cycle hooks that you understand and remember", description: 'Sensible life-cycle hooks that you understand and remember',
url: "", url: '',
}, },
{ {
icon: "️💡", icon: '️💡',
title: "Familiar.", title: 'Familiar.',
description: description:
"Use the built-in JSX-like syntax or bring your own custom templating", 'Use the built-in JSX-like syntax or bring your own custom templating',
url: "https://codepen.io/ayoayco-the-styleful/pen/ZEwNJBR?editors=1010", url: 'https://codepen.io/ayoayco-the-styleful/pen/ZEwNJBR?editors=1010',
}, },
{ {
icon: "️🛜", icon: '️🛜',
title: "Powerful.", title: 'Powerful.',
description: description:
"Attach 'side effects' that gets triggered on property value changes", "Attach 'side effects' that gets triggered on property value changes",
url: "", url: '',
}, },
]; ]
/** /**
* @type {Array<HTMLArticleElement>} * @type {Array<HTMLArticleElement>}
*/ */
get articleEl() { get articleEl() {
return this.querySelectorAll("article"); return this.querySelectorAll('article')
} }
afterViewInit() { afterViewInit() {
@ -46,29 +46,29 @@ class FeatureSet extends WebComponent {
* @type {Partial<CSSStyleDeclaration>} * @type {Partial<CSSStyleDeclaration>}
*/ */
const articleStyles = { const articleStyles = {
border: "1px solid #ccc", border: '1px solid #ccc',
borderRadius: "5px", borderRadius: '5px',
padding: "30px", padding: '30px',
margin: "0 auto 1em", margin: '0 auto 1em',
boxShadow: "5px 25px 10px -25px rgba(34, 34, 34, 0.15)", boxShadow: '5px 25px 10px -25px rgba(34, 34, 34, 0.15)',
}; }
Object.keys(articleStyles).forEach((rule) => Object.keys(articleStyles).forEach((rule) =>
this.articleEl.forEach((el) => (el.style[rule] = articleStyles[rule])), this.articleEl.forEach((el) => (el.style[rule] = articleStyles[rule]))
); )
/** /**
* @type {Partial<CSSStyleDeclaration>} * @type {Partial<CSSStyleDeclaration>}
*/ */
const ftrStyles = { const ftrStyles = {
maxWidth: "800px", maxWidth: '800px',
margin: "0 auto", margin: '0 auto',
padding: "30px", padding: '30px',
gap: "1em", gap: '1em',
}; }
const featureWrapper = this.querySelector(".feature-wrapper"); const featureWrapper = this.querySelector('.feature-wrapper')
Object.keys(ftrStyles).forEach( Object.keys(ftrStyles).forEach(
(rule) => (featureWrapper.style[rule] = ftrStyles[rule]), (rule) => (featureWrapper.style[rule] = ftrStyles[rule])
); )
} }
get template() { get template() {
@ -84,9 +84,9 @@ class FeatureSet extends WebComponent {
${feature.description} ${feature.description}
</p> </p>
</article> </article>
`, `
)} )}
</div> </div>
`; `
} }
} }

View file

@ -1,10 +1,10 @@
class Counter extends WebComponent { class Counter extends WebComponent {
static props = { static props = {
count: 0, count: 0,
}; }
get template() { get template() {
return html`<button onClick=${() => ++this.props.count}> return html`<button onClick=${() => ++this.props.count}>
${this.props.count} ${this.props.count}
</button>`; </button>`
} }
} }

View file

@ -27,7 +27,7 @@
--color-fade: #416fff; --color-fade: #416fff;
} }
body { body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px; max-width: 800px;
margin: 0 auto; margin: 0 auto;
padding: 1em; padding: 1em;

View file

@ -1,22 +1,22 @@
class HelloWorld extends HTMLElement { class HelloWorld extends HTMLElement {
static get observedAttributes() { static get observedAttributes() {
return ["my-name"]; return ['my-name']
} }
connectedCallback() { connectedCallback() {
let count = 0; let count = 0
const currentName = this.getAttribute("my-name"); const currentName = this.getAttribute('my-name')
if (!currentName) { if (!currentName) {
this.setAttribute("my-name", "World"); this.setAttribute('my-name', 'World')
} }
this.onclick = () => this.setAttribute("my-name", `Clicked ${++count}x`); this.onclick = () => this.setAttribute('my-name', `Clicked ${++count}x`)
} }
attributeChangedCallback(property, previousValue, currentValue) { attributeChangedCallback(property, previousValue, currentValue) {
if (property === "my-name" && previousValue !== currentValue) { if (property === 'my-name' && previousValue !== currentValue) {
this.innerHTML = `<button style="cursor:pointer">Hello ${currentValue}!</button>`; this.innerHTML = `<button style="cursor:pointer">Hello ${currentValue}!</button>`
} }
} }
} }

View file

@ -10,20 +10,20 @@
<script src="prism.js" defer></script> <script src="prism.js" defer></script>
<script server:setup> <script server:setup>
const project = { const project = {
name: "WebComponent.io", name: 'WebComponent.io',
description: "A simple reactivity system for web components", description: 'A simple reactivity system for web components',
}; }
const author = { const author = {
name: "Ayo Ayco", name: 'Ayo Ayco',
url: "https://ayco.io", url: 'https://ayco.io',
year: "2023", year: '2023',
}; }
</script> </script>
<style> <style>
@counter-style publish-icons { @counter-style publish-icons {
system: cyclic; system: cyclic;
symbols: "️✅"; symbols: '️✅';
suffix: " "; suffix: ' ';
} }
main { main {
font-size: large; font-size: large;

View file

@ -9,31 +9,31 @@ import {
getCamelCase, getCamelCase,
serialize, serialize,
deserialize, deserialize,
} from "./utils/index.js"; } from './utils/index.js'
/** /**
* A minimal base class to reduce the complexity of creating reactive custom elements * A minimal base class to reduce the complexity of creating reactive custom elements
* @see https://WebComponent.io * @see https://WebComponent.io
*/ */
export class WebComponent extends HTMLElement { export class WebComponent extends HTMLElement {
#host; #host
#prevDOM; #prevDOM
#props; #props
#typeMap = {}; #typeMap = {}
#effectsMap = {}; #effectsMap = {}
/** /**
* Array of strings that tells the browsers which attributes will cause a render * Array of strings that tells the browsers which attributes will cause a render
* @type {Array<string>} * @type {Array<string>}
*/ */
static properties = []; static properties = []
/** /**
* Blueprint for the Proxy props * Blueprint for the Proxy props
* @typedef {{[name: string]: any}} PropStringMap * @typedef {{[name: string]: any}} PropStringMap
* @type {PropStringMap} * @type {PropStringMap}
*/ */
static props; static props
/** /**
* Read-only string property that represents how the component will be rendered * Read-only string property that represents how the component will be rendered
@ -41,14 +41,14 @@ export class WebComponent extends HTMLElement {
* @see https://www.npmjs.com/package/web-component-base#template-vs-render * @see https://www.npmjs.com/package/web-component-base#template-vs-render
*/ */
get template() { get template() {
return ""; return ''
} }
/** /**
* Shadow root initialization options * Shadow root initialization options
* @type {ShadowRootInit} * @type {ShadowRootInit}
*/ */
static shadowRootInit; static shadowRootInit
/** /**
* Read-only property containing camelCase counterparts of observed attributes. * Read-only property containing camelCase counterparts of observed attributes.
@ -57,7 +57,7 @@ export class WebComponent extends HTMLElement {
* @type {PropStringMap} * @type {PropStringMap}
*/ */
get props() { get props() {
return this.#props; return this.#props
} }
/** /**
@ -87,126 +87,126 @@ export class WebComponent extends HTMLElement {
onChanges(changes) {} onChanges(changes) {}
constructor() { constructor() {
super(); super()
this.#initializeProps(); this.#initializeProps()
this.#initializeHost(); this.#initializeHost()
} }
static get observedAttributes() { static get observedAttributes() {
const propKeys = this.props const propKeys = this.props
? Object.keys(this.props).map((camelCase) => getKebabCase(camelCase)) ? Object.keys(this.props).map((camelCase) => getKebabCase(camelCase))
: []; : []
return [...new Set([...this.properties, ...propKeys])]; return [...new Set([...this.properties, ...propKeys])]
} }
connectedCallback() { connectedCallback() {
this.onInit(); this.onInit()
this.render(); this.render()
this.afterViewInit(); this.afterViewInit()
} }
disconnectedCallback() { disconnectedCallback() {
this.onDestroy(); this.onDestroy()
} }
attributeChangedCallback(property, previousValue, currentValue) { attributeChangedCallback(property, previousValue, currentValue) {
const camelCaps = getCamelCase(property); const camelCaps = getCamelCase(property)
if (previousValue !== currentValue) { if (previousValue !== currentValue) {
this[property] = currentValue === "" || currentValue; this[property] = currentValue === '' || currentValue
this[camelCaps] = this[property]; this[camelCaps] = this[property]
this.#handleUpdateProp(camelCaps, this[property]); this.#handleUpdateProp(camelCaps, this[property])
this.render(); this.render()
this.onChanges({ property, previousValue, currentValue }); this.onChanges({ property, previousValue, currentValue })
} }
} }
#handleUpdateProp(key, stringifiedValue) { #handleUpdateProp(key, stringifiedValue) {
const restored = deserialize(stringifiedValue, this.#typeMap[key]); const restored = deserialize(stringifiedValue, this.#typeMap[key])
if (restored !== this.props[key]) this.props[key] = restored; if (restored !== this.props[key]) this.props[key] = restored
} }
#handler(setter, meta) { #handler(setter, meta) {
const effectsMap = meta.#effectsMap; const effectsMap = meta.#effectsMap
const typeMap = meta.#typeMap; const typeMap = meta.#typeMap
return { return {
set(obj, prop, value) { set(obj, prop, value) {
const oldValue = obj[prop]; const oldValue = obj[prop]
if (!(prop in typeMap)) { if (!(prop in typeMap)) {
typeMap[prop] = typeof value; typeMap[prop] = typeof value
} }
if (value.attach === "effect") { if (value.attach === 'effect') {
if (!effectsMap[prop]) { if (!effectsMap[prop]) {
effectsMap[prop] = []; effectsMap[prop] = []
} }
effectsMap[prop].push(value.callback); effectsMap[prop].push(value.callback)
} else if (typeMap[prop] !== typeof value) { } else if (typeMap[prop] !== typeof value) {
throw TypeError( throw TypeError(
`Cannot assign ${typeof value} to ${ `Cannot assign ${typeof value} to ${
typeMap[prop] typeMap[prop]
} property (setting '${prop}' of ${meta.constructor.name})` } property (setting '${prop}' of ${meta.constructor.name})`
); )
} else if (oldValue !== value) { } else if (oldValue !== value) {
obj[prop] = value; obj[prop] = value
effectsMap[prop]?.forEach((f) => f(value)); effectsMap[prop]?.forEach((f) => f(value))
const kebab = getKebabCase(prop); const kebab = getKebabCase(prop)
setter(kebab, serialize(value)); setter(kebab, serialize(value))
} }
return true; return true
}, },
get(obj, prop) { get(obj, prop) {
// TODO: handle non-objects // TODO: handle non-objects
if (obj[prop] !== null && obj[prop] !== undefined) { if (obj[prop] !== null && obj[prop] !== undefined) {
Object.getPrototypeOf(obj[prop]).proxy = meta.#props; Object.getPrototypeOf(obj[prop]).proxy = meta.#props
Object.getPrototypeOf(obj[prop]).prop = prop; Object.getPrototypeOf(obj[prop]).prop = prop
} }
return obj[prop]; return obj[prop]
}, },
}; }
} }
#initializeProps() { #initializeProps() {
let initialProps = structuredClone(this.constructor.props) ?? {}; let initialProps = structuredClone(this.constructor.props) ?? {}
Object.keys(initialProps).forEach((camelCase) => { Object.keys(initialProps).forEach((camelCase) => {
const value = initialProps[camelCase]; const value = initialProps[camelCase]
this.#typeMap[camelCase] = typeof value; this.#typeMap[camelCase] = typeof value
this.setAttribute(getKebabCase(camelCase), serialize(value)); this.setAttribute(getKebabCase(camelCase), serialize(value))
}); })
if (!this.#props) { if (!this.#props) {
this.#props = new Proxy( this.#props = new Proxy(
initialProps, initialProps,
this.#handler((key, value) => this.setAttribute(key, value), this) this.#handler((key, value) => this.setAttribute(key, value), this)
); )
} }
} }
#initializeHost() { #initializeHost() {
this.#host = this; this.#host = this
if (this.constructor.shadowRootInit) { if (this.constructor.shadowRootInit) {
this.#host = this.attachShadow(this.constructor.shadowRootInit); this.#host = this.attachShadow(this.constructor.shadowRootInit)
} }
} }
render() { render() {
if (typeof this.template === "string") { if (typeof this.template === 'string') {
this.innerHTML = this.template; this.innerHTML = this.template
} else if (typeof this.template === "object") { } else if (typeof this.template === 'object') {
const tree = this.template; const tree = this.template
// TODO: smart diffing // TODO: smart diffing
if (JSON.stringify(this.#prevDOM) !== JSON.stringify(tree)) { if (JSON.stringify(this.#prevDOM) !== JSON.stringify(tree)) {
const el = createElement(tree); const el = createElement(tree)
if (el) { if (el) {
if (Array.isArray(el)) this.#host.replaceChildren(...el); if (Array.isArray(el)) this.#host.replaceChildren(...el)
else this.#host.replaceChildren(el); else this.#host.replaceChildren(el)
} }
this.#prevDOM = tree; this.#prevDOM = tree
} }
} }
} }

View file

@ -4,10 +4,10 @@
* @param {(newValue: any) => void} callback * @param {(newValue: any) => void} callback
*/ */
export function attachEffect(obj, callback) { export function attachEffect(obj, callback) {
const { proxy, prop } = Object.getPrototypeOf(obj); const { proxy, prop } = Object.getPrototypeOf(obj)
proxy[prop] = { proxy[prop] = {
attach: "effect", attach: 'effect',
callback, callback,
}; }
} }

View file

@ -6,15 +6,15 @@ const htm =
l, l,
s = arguments, s = arguments,
t = 1, t = 1,
u = "", u = '',
r = "", r = '',
o = [0], o = [0],
f = function (n) { f = function (n) {
1 === t && (n || (u = u.replace(/^\s*\n\s*|\s*\n\s*$/g, ""))) 1 === t && (n || (u = u.replace(/^\s*\n\s*|\s*\n\s*$/g, '')))
? o.push(n ? s[n] : u) ? o.push(n ? s[n] : u)
: 3 === t && (n || u) : 3 === t && (n || u)
? ((o[1] = n ? s[n] : u), (t = 2)) ? ((o[1] = n ? s[n] : u), (t = 2))
: 2 === t && "..." === u && n : 2 === t && '...' === u && n
? (o[2] = Object.assign(o[2] || {}, s[n])) ? (o[2] = Object.assign(o[2] || {}, s[n]))
: 2 === t && u && !n : 2 === t && u && !n
? ((o[2] = o[2] || {})[u] = !0) ? ((o[2] = o[2] || {})[u] = !0)
@ -27,50 +27,50 @@ const htm =
: u), : u),
(t = 6)) (t = 6))
: (n || u) && (o[2][l] += n ? u + s[n] : u)), : (n || u) && (o[2][l] += n ? u + s[n] : u)),
(u = ""); (u = '')
}, },
i = 0; i = 0;
i < n.length; i < n.length;
i++ i++
) { ) {
i && (1 === t && f(), f(i)); i && (1 === t && f(), f(i))
for (var p = 0; p < n[i].length; p++) for (var p = 0; p < n[i].length; p++)
(e = n[i][p]), (e = n[i][p]),
1 === t 1 === t
? "<" === e ? '<' === e
? (f(), (o = [o, "", null]), (t = 3)) ? (f(), (o = [o, '', null]), (t = 3))
: (u += e) : (u += e)
: 4 === t : 4 === t
? "--" === u && ">" === e ? '--' === u && '>' === e
? ((t = 1), (u = "")) ? ((t = 1), (u = ''))
: (u = e + u[0]) : (u = e + u[0])
: r : r
? e === r ? e === r
? (r = "") ? (r = '')
: (u += e) : (u += e)
: '"' === e || "'" === e : '"' === e || "'" === e
? (r = e) ? (r = e)
: ">" === e : '>' === e
? (f(), (t = 1)) ? (f(), (t = 1))
: t && : t &&
("=" === e ('=' === e
? ((t = 5), (l = u), (u = "")) ? ((t = 5), (l = u), (u = ''))
: "/" === e && (t < 5 || ">" === n[i][p + 1]) : '/' === e && (t < 5 || '>' === n[i][p + 1])
? (f(), ? (f(),
3 === t && (o = o[0]), 3 === t && (o = o[0]),
(t = o), (t = o),
(o = o[0]).push(this.apply(null, t.slice(1))), (o = o[0]).push(this.apply(null, t.slice(1))),
(t = 0)) (t = 0))
: " " === e || "\t" === e || "\n" === e || "\r" === e : ' ' === e || '\t' === e || '\n' === e || '\r' === e
? (f(), (t = 2)) ? (f(), (t = 2))
: (u += e)), : (u += e)),
3 === t && "!--" === u && ((t = 4), (o = o[0])); 3 === t && '!--' === u && ((t = 4), (o = o[0]))
} }
return f(), o.length > 2 ? o.slice(1) : o[1]; return f(), o.length > 2 ? o.slice(1) : o[1]
}); })
function h(type, props, ...children) { function h(type, props, ...children) {
return { type, props, children }; return { type, props, children }
} }
/** /**
@ -78,4 +78,4 @@ function h(type, props, ...children) {
* @license Apache <https://www.apache.org/licenses/LICENSE-2.0> * @license Apache <https://www.apache.org/licenses/LICENSE-2.0>
* @author Jason Miller <jason@developit.ca> * @author Jason Miller <jason@developit.ca>
*/ */
export const html = htm.bind(h); export const html = htm.bind(h)

View file

@ -1,3 +1,3 @@
export { attachEffect } from "./attach-effect.js"; export { attachEffect } from './attach-effect.js'
export { WebComponent } from "./WebComponent.js"; export { WebComponent } from './WebComponent.js'
export { html } from "./html.js"; export { html } from './html.js'

View file

@ -1,46 +1,46 @@
import { serialize } from "./serialize.mjs"; import { serialize } from './serialize.mjs'
export function createElement(tree) { export function createElement(tree) {
if (!tree.type) { if (!tree.type) {
if (Array.isArray(tree)) { if (Array.isArray(tree)) {
const frag = document.createDocumentFragment(); const frag = document.createDocumentFragment()
frag.replaceChildren(...tree.map((leaf) => createElement(leaf))); frag.replaceChildren(...tree.map((leaf) => createElement(leaf)))
return frag; return frag
} }
return document.createTextNode(tree); return document.createTextNode(tree)
} else { } else {
const el = document.createElement(tree.type); const el = document.createElement(tree.type)
/** /**
* handle props * handle props
*/ */
if (tree.props) { if (tree.props) {
Object.entries(tree.props).forEach(([prop, value]) => { Object.entries(tree.props).forEach(([prop, value]) => {
const domProp = prop.toLowerCase(); const domProp = prop.toLowerCase()
if (domProp === "style" && typeof value === "object" && !!value) { if (domProp === 'style' && typeof value === 'object' && !!value) {
applyStyles(el, value); applyStyles(el, value)
} else if (prop in el) { } else if (prop in el) {
el[prop] = value; el[prop] = value
} else if (domProp in el) { } else if (domProp in el) {
el[domProp] = value; el[domProp] = value
} else { } else {
el.setAttribute(prop, serialize(value)); el.setAttribute(prop, serialize(value))
} }
}); })
} }
/** /**
* handle children * handle children
*/ */
tree.children?.forEach((child) => { tree.children?.forEach((child) => {
const childEl = createElement(child); const childEl = createElement(child)
if (childEl instanceof Node) { if (childEl instanceof Node) {
el.appendChild(childEl); el.appendChild(childEl)
} }
}); })
return el; return el
} }
} }
function applyStyles(el, styleObj) { function applyStyles(el, styleObj) {
Object.entries(styleObj).forEach(([rule, value]) => { Object.entries(styleObj).forEach(([rule, value]) => {
if (rule in el.style && value) el.style[rule] = value; if (rule in el.style && value) el.style[rule] = value
}); })
} }

View file

@ -1,11 +1,11 @@
export function deserialize(value, type) { export function deserialize(value, type) {
switch (type) { switch (type) {
case "number": case 'number':
case "boolean": case 'boolean':
case "object": case 'object':
case "undefined": case 'undefined':
return JSON.parse(value); return JSON.parse(value)
default: default:
return value; return value
} }
} }

View file

@ -1,3 +1,3 @@
export function getCamelCase(kebab) { export function getCamelCase(kebab) {
return kebab.replace(/-./g, (x) => x[1].toUpperCase()); return kebab.replace(/-./g, (x) => x[1].toUpperCase())
} }

View file

@ -1,6 +1,6 @@
export function getKebabCase(str) { export function getKebabCase(str) {
return str.replace( return str.replace(
/[A-Z]+(?![a-z])|[A-Z]/g, /[A-Z]+(?![a-z])|[A-Z]/g,
($, ofs) => (ofs ? "-" : "") + $.toLowerCase(), ($, ofs) => (ofs ? '-' : '') + $.toLowerCase()
); )
} }

View file

@ -1,5 +1,5 @@
export { serialize } from "./serialize.mjs"; export { serialize } from './serialize.mjs'
export { deserialize } from "./deserialize.mjs"; export { deserialize } from './deserialize.mjs'
export { getCamelCase } from "./get-camel-case.mjs"; export { getCamelCase } from './get-camel-case.mjs'
export { getKebabCase } from "./get-kebab-case.mjs"; export { getKebabCase } from './get-kebab-case.mjs'
export { createElement } from "./create-element.mjs"; export { createElement } from './create-element.mjs'

View file

@ -1,10 +1,10 @@
export function serialize(value) { export function serialize(value) {
switch (typeof value) { switch (typeof value) {
case "number": case 'number':
case "boolean": case 'boolean':
case "object": case 'object':
return JSON.stringify(value); return JSON.stringify(value)
default: default:
return value; return value
} }
} }

View file

@ -1,27 +1,27 @@
import { describe, expect, test } from "vitest"; import { describe, expect, test } from 'vitest'
import { serialize } from "../src/utils/serialize.mjs"; import { serialize } from '../src/utils/serialize.mjs'
describe("serialize", () => { describe('serialize', () => {
test("should stringify number", () => { test('should stringify number', () => {
const result = serialize(3); const result = serialize(3)
expect(result).toBeTypeOf("string"); expect(result).toBeTypeOf('string')
expect(result).toEqual("3"); expect(result).toEqual('3')
}); })
test("should stringify boolean", () => { test('should stringify boolean', () => {
const result = serialize(false); const result = serialize(false)
expect(result).toBeTypeOf("string"); expect(result).toBeTypeOf('string')
expect(result).toEqual("false"); expect(result).toEqual('false')
}); })
test("should stringify object", () => { test('should stringify object', () => {
const result = serialize({ hello: "world" }); const result = serialize({ hello: 'world' })
expect(result).toBeTypeOf("string"); expect(result).toBeTypeOf('string')
expect(result).toEqual('{"hello":"world"}'); expect(result).toEqual('{"hello":"world"}')
}); })
test("should return undefined", () => { test('should return undefined', () => {
const result = serialize(undefined); const result = serialize(undefined)
expect(result).toBeUndefined(); expect(result).toBeUndefined()
}); })
}); })