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:
push:
branches: ["main"]
branches: ['main']
pull_request:
# The branches below must be a subset of the branches above
branches: ["main"]
branches: ['main']
schedule:
- cron: "36 3 * * 2"
- cron: '36 3 * * 2'
jobs:
eslint:

View file

@ -1,4 +1,4 @@
{
"js/ts.implicitProjectConfig.checkJs": 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.
Links:
- [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)
## Table of Contents
1. [Project Status](#project-status)
1. [Installation](#installation)
1. [Import via CDN](#import-via-cdn)
1. [Installation via npm](#installation-via-npm)
1. [Import via CDN](#import-via-cdn)
1. [Installation via npm](#installation-via-npm)
1. [Exports](#exports)
1. [Main Exports](#main-exports)
1. [Utilities](#utilities)
1. [Main Exports](#main-exports)
1. [Utilities](#utilities)
1. [Usage](#usage)
1. [Examples](#Examples)
1. [To-Do App](#1-to-do-app)
1. [Single HTML file](#2-single-html-file-example)
1. [Feature Demos](#3-feature-demos)
1. [To-Do App](#1-to-do-app)
1. [Single HTML file](#2-single-html-file-example)
1. [Feature Demos](#3-feature-demos)
1. [`template` vs `render()`](#template-vs-render)
1. [Prop access](#prop-access)
1. [Alternatives](#alternatives)
1. [Alternatives](#alternatives)
1. [Styling](#styling)
1. [Shadow DOM Opt-In](#shadow-dom-opt-in)
1. [Just the Templating](#just-the-templating)
1. [Life-Cycle Hooks](#life-cycle-hooks)
1. [`onInit`](#oninit) - the component is connected to the DOM, before view is initialized
1. [`afterViewInit`](#afterviewinit) - after the view is first initialized
1. [`onDestroy`](#ondestroy) - the component is disconnected from the DOM
1. [`onChanges`](#onchanges) - every time an attribute value changes
1. [`onInit`](#oninit) - the component is connected to the DOM, before view is initialized
1. [`afterViewInit`](#afterviewinit) - after the view is first initialized
1. [`onDestroy`](#ondestroy) - the component is disconnected from the DOM
1. [`onChanges`](#onchanges) - every time an attribute value changes
1. [Library Size](#library-size)
## 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! :)
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.
## 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.
### 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.
```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
Usable for projects with bundlers or using import maps pointing to the specific files downloaded in `node_modules/web-component-base`.
```bash
@ -81,31 +86,37 @@ You can import everything separately, or in a single file each for the main expo
```js
// all in a single file
import { WebComponent, html, attachEffect } from "web-component-base";
import { WebComponent, html, attachEffect } from 'web-component-base'
// 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
```js
// 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
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...
```
## Usage
@ -114,21 +125,21 @@ In your component class:
```js
// 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 {
static props ={
static props = {
myName: 'World',
emotion: 'sad'
emotion: 'sad',
}
get template() {
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:
@ -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.
```html
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<title>WC Base Test</title>
<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 {
static props = {
myName: 'World'
myName: 'World',
}
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>
</head>
<body>
<hello-world my-name="Ayo"></hello-world>
<script>
const helloWorld = document.querySelector('hello-world');
setTimeout(() => {
helloWorld.props.myName = 'Ayo zzzZzzz';
}, 2500);
const helloWorld = document.querySelector('hello-world')
setTimeout(() => {
helloWorld.props.myName = 'Ayo zzzZzzz'
}, 2500)
</script>
</body>
</html>
```
### 3. Feature 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. [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)
@ -209,25 +222,23 @@ Some feature-specific demos:
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. 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. 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. 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
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
class HelloWorld extends WebComponent {
static props = {
myProp: 'World'
myProp: 'World',
}
get template() {
return html`
<h1>Hello ${this.props.myProp}</h1>
`;
return html` <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.
For example, assigning a value like so:
```
this.props.myName = 'hello'
```
...is like calling the following:
```
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
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. 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).
Try it now with this [example on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/bGzXjwQ?editors=1010)
```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 {
static props = {
emphasize: false,
type: "warn",
};
type: 'warn',
}
#typeStyles = {
warn: {
backgroundColor: "yellow",
border: "1px solid orange",
backgroundColor: 'yellow',
border: '1px solid orange',
},
error: {
backgroundColor: "orange",
border: "1px solid red",
backgroundColor: 'orange',
border: '1px solid red',
},
};
}
get template() {
return html`
<div
style=${{
...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>
`;
`
}
}
customElements.define("styled-elements", StyledElements);
customElements.define('styled-elements', StyledElements)
```
## 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.
Try it now [on CodePen ↗](https://codepen.io/ayoayco-the-styleful/pen/VwRYVPv?editors=1010)
Example:
```js
static shadowRootInit = {
mode: "closed",
};
```
## 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.
@ -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).
```js
import {html} from 'https://unpkg.com/web-component-base/html'
import {createElement} from 'https://unpkg.com/web-component-base/utils'
import { html } from 'https://unpkg.com/web-component-base/html'
import { createElement } from 'https://unpkg.com/web-component-base/utils'
class MyQuote extends HTMLElement {
connectedCallback() {
const el = createElement(html`
<button onClick=${() => alert('hey')}>
hey
</button>`);
const el = createElement(
html` <button onClick=${() => alert('hey')}>hey</button>`
)
this.appendChild(el)
}
}
customElements.define('my-quote', MyQuote)
```
## Life-Cycle Hooks
Define behavior when certain events in the component's life cycle is triggered by providing hook methods
### onInit()
- Triggered when the component is connected to the DOM
- Best for setting up the component
```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 {
// gets called when the component is used in an HTML document
onInit() {
this.onclick = () => console.log(">>> click!");
this.onclick = () => console.log('>>> click!')
}
get template() {
return `<span style="cursor:pointer">Click me!</span>`;
return `<span style="cursor:pointer">Click me!</span>`
}
}
```
### afterViewInit()
- Triggered after the view is first initialized
- Triggered after the view is first initialized
```js
class ClickableText extends WebComponent {
// gets called when the component's innerHTML is first filled
afterViewInit() {
const footer = this.querySelector('footer');
const footer = this.querySelector('footer')
// do stuff to footer after view is initialized
}
get template() {
return `<footer>Awesome site &copy; 2023</footer>`;
return `<footer>Awesome site &copy; 2023</footer>`
}
}
```
### onDestroy()
- Triggered when the component is disconnected from the DOM
- best for undoing any setup done in `onInit()`
```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 {
clickCallback() {
console.log(">>> click!");
console.log('>>> click!')
}
onInit() {
this.onclick = this.clickCallback;
this.onclick = this.clickCallback
}
onDestroy() {
console.log(">>> removing event listener");
this.removeEventListener("click", this.clickCallback);
console.log('>>> removing event listener')
this.removeEventListener('click', this.clickCallback)
}
get template() {
return `<span style="cursor:pointer">Click me!</span>`;
return `<span style="cursor:pointer">Click me!</span>`
}
}
```
### onChanges()
- Triggered when an attribute value changed
```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 {
// gets called when an attribute value changes
onChanges(changes) {
const {property, previousValue, currentValue} = changes;
console.log('>>> ', {property, previousValue, currentValue})
const { property, previousValue, currentValue } = changes
console.log('>>> ', { property, previousValue, currentValue })
}
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.

View file

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

View file

@ -1,21 +1,23 @@
// @ts-check
import { WebComponent, attachEffect, html } from "../../src/index.js";
import { WebComponent, attachEffect, html } from '../../src/index.js'
export class Counter extends WebComponent {
static props = {
count: 0,
};
}
onInit() {
attachEffect(this.props.count, (count) => console.log(count));
attachEffect(this.props.count, (count) => console.log(count))
}
afterViewInit() {
attachEffect(this.props.count, (count) => console.log(count + 100));
attachEffect(this.props.count, (count) => console.log(count + 100))
}
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
import { WebComponent, attachEffect, html } from "../../src/index.js";
import { WebComponent, attachEffect, html } from '../../src/index.js'
export class Decrease extends WebComponent {
static props = {
count: 999,
};
}
onInit() {
attachEffect(this.props.count, (count) => console.log(count));
attachEffect(this.props.count, (count) => console.log(count))
}
afterViewInit() {
attachEffect(this.props.count, (count) => console.log(count + 100));
attachEffect(this.props.count, (count) => console.log(count + 100))
}
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">
<head>
<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 {
static props = {
isInline: false,
anotherone: false,
};
}
get template() {
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
import { WebComponent, html } from "../../src/index.js";
import { WebComponent, html } from '../../src/index.js'
export class Counter extends WebComponent {
static props = {
count: 0,
};
}
get template() {
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,24 +1,26 @@
// @ts-check
import { html, WebComponent } from "../../src/index.js";
import { html, WebComponent } from '../../src/index.js'
export class HelloWorld extends WebComponent {
static props = {
count: 0,
emotion: "sad",
};
emotion: 'sad',
}
onInit() {
this.props.count = 0;
this.props.count = 0
}
get template() {
const label = this.props.count ? `Clicked ${this.props.count}` : "World";
const emote = this.props.emotion === "sad" ? ". 😭" : "! 🙌";
const label = this.props.count ? `Clicked ${this.props.count}` : 'World'
const emote = this.props.emotion === 'sad' ? '. 😭' : '! 🙌'
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
import { html, WebComponent } from "../../src/index.js";
import { html, WebComponent } from '../../src/index.js'
class SimpleText extends WebComponent {
clickCallback() {
console.log(">>> click!");
console.log('>>> click!')
}
onDestroy() {
console.log(">>> removing event listener");
this.removeEventListener("click", this.clickCallback);
console.log('>>> removing event listener')
this.removeEventListener('click', this.clickCallback)
}
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 {
static props = {
toggle: false,
};
}
get template() {
return html`
<button onClick=${() => (this.props.toggle = !this.props.toggle)}>
${this.props.toggle}
</button>
`;
`
}
}
customElements.define("my-toggle", Toggle);
customElements.define('my-toggle', Toggle)

View file

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

View file

@ -13,33 +13,35 @@
import {
WebComponent,
html,
} from "https://esm.sh/web-component-base@latest";
} from 'https://esm.sh/web-component-base@latest'
export class Counter extends WebComponent {
static props = {
count: 0,
};
}
get template() {
return html`<button onClick=${() => ++this.props.count}>
${this.props.count}
</button>`;
</button>`
}
}
class Toggle extends WebComponent {
static props = {
toggle: false,
};
}
clickFn = () => (this.props.toggle = !this.props.toggle);
clickFn = () => (this.props.toggle = !this.props.toggle)
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-toggle", Toggle);
customElements.define('my-counter', Counter)
customElements.define('my-toggle', Toggle)
</script>
</head>
<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 {
static props = {
myName: "World",
};
myName: 'World',
}
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">
<head>
<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 {
static props = {
count: 123,
};
}
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
import { WebComponent, html } from "../../src/index.js";
import { WebComponent, html } from '../../src/index.js'
class StyledElements extends WebComponent {
static props = {
condition: false,
type: "info",
};
type: 'info',
}
#typeStyles = {
info: {
backgroundColor: "blue",
border: "1px solid green",
backgroundColor: 'blue',
border: '1px solid green',
},
warn: {
backgroundColor: "yellow",
border: "1px solid orange",
backgroundColor: 'yellow',
border: '1px solid orange',
},
error: {
backgroundColor: "orange",
border: "1px solid red",
backgroundColor: 'orange',
border: '1px solid red',
},
};
}
get template() {
return html`
<div
style=${{
...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>
`;
`
}
}
customElements.define("styled-elements", StyledElements);
customElements.define('styled-elements', StyledElements)

View file

@ -1,22 +1,22 @@
// @ts-check
import { WebComponent, html } from "../../src/index.js";
import { WebComponent, html } from '../../src/index.js'
export class Counter extends WebComponent {
static props = {
count: 123,
};
}
get template() {
const list = ["a", "b", "c", "what"];
const list = ['a', 'b', 'c', 'what']
const links = [
{
url: "https://ayco.io",
text: "Ayo Ayco",
url: 'https://ayco.io',
text: 'Ayo Ayco',
},
{
url: "https://ayco.io/gh/McFly",
text: "McFly",
url: 'https://ayco.io/gh/McFly',
text: 'McFly',
},
];
]
return html`
<button
@ -39,11 +39,11 @@ export class Counter extends WebComponent {
<ul>
${links.map(
(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>
`;
`
}
}
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 {
html,
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 {
static props = {
count: 123,
};
}
get template() {
const list = ["a", "b", "c", "what"];
const list = ['a', 'b', 'c', 'what']
const links = [
{
url: "https://ayco.io",
text: "Ayo Ayco",
url: 'https://ayco.io',
text: 'Ayo Ayco',
},
{
url: "https://ayco.io/gh/McFly",
text: "McFly",
url: 'https://ayco.io/gh/McFly',
text: 'McFly',
},
];
]
return html`
<button
@ -42,14 +42,14 @@ export class LitCounter extends WebComponent {
<ul>
${links.map(
(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>
`;
`
}
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
import { html, WebComponent } from "../../src/index.js";
import { html, WebComponent } from '../../src/index.js'
export class Counter extends WebComponent {
static props = {
count: 1,
};
}
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 {
static props = {
name: "a",
};
addA = () => (this.props.name += "a");
name: 'a',
}
addA = () => (this.props.name += 'a')
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
@ -6,45 +6,43 @@ import { html, WebComponent } from "../../src/index.js";
export class ObjectText extends WebComponent {
static props = {
object: {
hello: "worldzz",
hello: 'worldzz',
age: 2,
},
};
}
onChanges() {
console.log(">>> object", this.props.object);
console.log('>>> object', this.props.object)
}
get template() {
return html`
<form>
<label for="greeting-field">Hello</label>
<textarea
onkeyup=${
(event) => {
this.props.object = {
...this.props.object,
hello: event.target.value,
};
onkeyup=${(event) => {
this.props.object = {
...this.props.object,
hello: event.target.value,
}
}
id="greeting-field">
}}
id="greeting-field"
>
${this.props.object.hello}
</textarea>
</textarea
>
<label for="age-field">Age</label>
<input
onkeyup=${
(event) => {
this.props.object = {
...this.props.object,
age: event.target.value,
};
onkeyup=${(event) => {
this.props.object = {
...this.props.object,
age: event.target.value,
}
}
id="age-field" value=${this.props.object.age} />
}}
id="age-field"
value=${this.props.object.age}
/>
</form>
`;
`
}
}
customElements.define("my-object", ObjectText);
customElements.define('my-object', ObjectText)

View file

@ -1,18 +1,20 @@
// @ts-check
import { html, WebComponent } from "../../src/index.js";
import { html, WebComponent } from '../../src/index.js'
export class Toggle extends WebComponent {
static props = {
toggle: false,
};
}
handleToggle() {
this.props.toggle = !this.props.toggle;
this.props.toggle = !this.props.toggle
}
get template() {
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">
<head>
<meta charset="UTF-8" />
@ -10,33 +10,27 @@
<script type="module" src="./Object.mjs"></script>
<style>
* {
font-size: larger
font-size: larger;
}
</style>
</head>
<body>
<div>
Counter: <my-counter></my-counter>
</div>
<div>
Toggle: <my-toggle></my-toggle>
</div>
<div>
String: <my-hello-world></my-hello-world>
</div>
<div>Counter: <my-counter></my-counter></div>
<div>Toggle: <my-toggle></my-toggle></div>
<div>String: <my-hello-world></my-hello-world></div>
<div>
<my-object></my-object>
<p id="display-panel"></p>
</div>
<script type="module">
import { attachEffect } from "../../src/index.js";
const myObjectEl = document.querySelector('my-object');
const objectProp = myObjectEl.props.object;
const displayPanelEl = document.querySelector('#display-panel');
displayPanelEl.textContent = JSON.stringify(objectProp);
import { attachEffect } from '../../src/index.js'
const myObjectEl = document.querySelector('my-object')
const objectProp = myObjectEl.props.object
const displayPanelEl = document.querySelector('#display-panel')
displayPanelEl.textContent = JSON.stringify(objectProp)
attachEffect(objectProp, (object) => {
displayPanelEl.textContent = JSON.stringify(object);
});
displayPanelEl.textContent = JSON.stringify(object)
})
</script>
</body>
</html>

View file

@ -1,33 +1,33 @@
// @ts-check
import { WebComponent, html } from "../../src/index.js";
import { WebComponent, html } from '../../src/index.js'
export class Counter extends WebComponent {
static props = {
count: 123,
};
}
static shadowRootInit = {
mode: "open",
};
mode: 'open',
}
get template() {
const list = ["a", "b", "c", "what"];
const list = ['a', 'b', 'c', 'what']
const links = [
{
url: "https://ayco.io",
text: "Ayo Ayco",
url: 'https://ayco.io',
text: 'Ayo Ayco',
},
{
url: "https://ayco.io/gh/McFly",
text: "McFly",
url: 'https://ayco.io/gh/McFly',
text: 'McFly',
},
];
]
return html`
<button
class="hey"
id="btn"
onClick=${() => ++this.props.count}
style=${{ backgroundColor: "green", color: "white" }}
style=${{ backgroundColor: 'green', color: 'white' }}
about="Elephant"
data-name="thing"
aria-name="thingz"
@ -48,8 +48,8 @@ export class Counter extends WebComponent {
</li>`
)}
</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:
# include packages in subfolders (e.g. apps/ and packages/)
- "site/**"
- 'site/**'

View file

@ -1,8 +1,7 @@
# McFly Starter Project
## Background
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)
@ -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.
## Features
The time has come for vanilla Web tech. 🎉
✅ Create web apps with vanilla custom elements<br>
@ -19,17 +19,21 @@ The time has come for vanilla Web tech. 🎉
✅ Deploy anywhere<br>
## Special directories
**1. `./src/pages/`**
- file-based routing for `.html` files
- 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
**2. `./src/components/`**
- 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>`
- static `.html` fragments; a `my-header.html` fragment can be directly used as `<my-header>`
**3. `./routes/api/`**
- file-based routing for REST API endpoints
- e.g., `./routes/api/users.ts` can be accessed via `http://<domain>/api/users`
- 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
```js
import defineConfig from "./packages/define-config";
import defineConfig from './packages/define-config'
export default defineConfig({
components: "js",
});
components: 'js',
})
```
## Commands
The following commands are available to you on this project. Add more, or modify them as needed in your `./package.json` file.
| Command | Action |
| --- | --- |
| npm start | Start the development server |
| npm run prepare | Prepare the workspace |
| npm run build | Locally generate the app's build files to `./output` |
| npm run preview | Preview the built app locally |
| Command | Action |
| --------------- | ---------------------------------------------------- |
| npm start | Start the development server |
| npm run prepare | Prepare the workspace |
| npm run build | Locally generate the app's build files to `./output` |
| 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({
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
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
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
var _self =
"undefined" != typeof window
'undefined' != typeof window
? window
: "undefined" != typeof WorkerGlobalScope &&
: 'undefined' != typeof WorkerGlobalScope &&
self instanceof WorkerGlobalScope
? self
: {},
@ -22,27 +22,27 @@ var _self =
: Array.isArray(n)
? n.map(e)
: n
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/\u00a0/g, " ");
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/\u00a0/g, ' ')
},
type: function (e) {
return Object.prototype.toString.call(e).slice(8, -1);
return Object.prototype.toString.call(e).slice(8, -1)
},
objId: function (e) {
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) {
var r, i;
var r, i
switch (((t = t || {}), a.util.type(n))) {
case "Object":
if (((i = a.util.objId(n)), t[i])) return t[i];
case 'Object':
if (((i = a.util.objId(n)), t[i])) return t[i]
for (var l in ((r = {}), (t[i] = r), n))
n.hasOwnProperty(l) && (r[l] = e(n[l], t));
return r;
case "Array":
n.hasOwnProperty(l) && (r[l] = e(n[l], t))
return r
case 'Array':
return (
(i = a.util.objId(n)),
t[i]
@ -50,49 +50,49 @@ var _self =
: ((r = []),
(t[i] = r),
n.forEach(function (n, a) {
r[a] = e(n, t);
r[a] = e(n, t)
}),
r)
);
)
default:
return n;
return n
}
},
getLanguage: function (e) {
for (; e; ) {
var t = n.exec(e.className);
if (t) return t[1].toLowerCase();
e = e.parentElement;
var t = n.exec(e.className)
if (t) return t[1].toLowerCase()
e = e.parentElement
}
return "none";
return 'none'
},
setLanguage: function (e, t) {
(e.className = e.className.replace(RegExp(n, "gi"), "")),
e.classList.add("language-" + t);
;(e.className = e.className.replace(RegExp(n, 'gi'), '')),
e.classList.add('language-' + t)
},
currentScript: function () {
if ("undefined" == typeof document) return null;
if ("currentScript" in document) return document.currentScript;
if ('undefined' == typeof document) return null
if ('currentScript' in document) return document.currentScript
try {
throw new Error();
throw new Error()
} catch (r) {
var e = (/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(r.stack) ||
[])[1];
[])[1]
if (e) {
var n = document.getElementsByTagName("script");
for (var t in n) if (n[t].src == e) return n[t];
var n = document.getElementsByTagName('script')
for (var t in n) if (n[t].src == e) return n[t]
}
return null;
return null
}
},
isActive: function (e, n, t) {
for (var r = "no-" + n; e; ) {
var a = e.classList;
if (a.contains(n)) return !0;
if (a.contains(r)) return !1;
e = e.parentElement;
for (var r = 'no-' + n; e; ) {
var a = e.classList
if (a.contains(n)) return !0
if (a.contains(r)) return !1
e = e.parentElement
}
return !!t;
return !!t
},
},
languages: {
@ -101,45 +101,45 @@ var _self =
text: r,
txt: r,
extend: function (e, n) {
var t = a.util.clone(a.languages[e]);
for (var r in n) t[r] = n[r];
return t;
var t = a.util.clone(a.languages[e])
for (var r in n) t[r] = n[r]
return t
},
insertBefore: function (e, n, t, r) {
var i = (r = r || a.languages)[e],
l = {};
l = {}
for (var o in i)
if (i.hasOwnProperty(o)) {
if (o == n)
for (var s in t) t.hasOwnProperty(s) && (l[s] = t[s]);
t.hasOwnProperty(o) || (l[o] = i[o]);
for (var s in t) t.hasOwnProperty(s) && (l[s] = t[s])
t.hasOwnProperty(o) || (l[o] = i[o])
}
var u = r[e];
var u = r[e]
return (
(r[e] = l),
a.languages.DFS(a.languages, function (n, t) {
t === u && n != e && (this[n] = l);
t === u && n != e && (this[n] = l)
}),
l
);
)
},
DFS: function e(n, t, r, i) {
i = i || {};
var l = a.util.objId;
i = i || {}
var l = a.util.objId
for (var o in n)
if (n.hasOwnProperty(o)) {
t.call(n, o, n[o], r || o);
t.call(n, o, n[o], r || o)
var s = n[o],
u = a.util.type(s);
"Object" !== u || i[l(s)]
? "Array" !== u || i[l(s)] || ((i[l(s)] = !0), e(s, t, o, i))
: ((i[l(s)] = !0), e(s, t, null, i));
u = a.util.type(s)
'Object' !== u || i[l(s)]
? 'Array' !== u || i[l(s)] || ((i[l(s)] = !0), e(s, t, o, i))
: ((i[l(s)] = !0), e(s, t, null, i))
}
},
},
plugins: {},
highlightAll: function (e, n) {
a.highlightAllUnder(document, e, n);
a.highlightAllUnder(document, e, n)
},
highlightAllUnder: function (e, n, t) {
var r = {
@ -147,162 +147,160 @@ var _self =
container: e,
selector:
'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.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++]); )
a.highlightElement(i, !0 === n, r.callback);
a.highlightElement(i, !0 === n, r.callback)
},
highlightElement: function (n, t, r) {
var i = a.util.getLanguage(n),
l = a.languages[i];
a.util.setLanguage(n, i);
var o = n.parentElement;
o && "pre" === o.nodeName.toLowerCase() && a.util.setLanguage(o, i);
var s = { element: n, language: i, grammar: l, code: n.textContent };
l = a.languages[i]
a.util.setLanguage(n, i)
var o = n.parentElement
o && 'pre' === o.nodeName.toLowerCase() && a.util.setLanguage(o, i)
var s = { element: n, language: i, grammar: l, code: n.textContent }
function u(e) {
(s.highlightedCode = e),
a.hooks.run("before-insert", s),
;(s.highlightedCode = e),
a.hooks.run('before-insert', s),
(s.element.innerHTML = s.highlightedCode),
a.hooks.run("after-highlight", s),
a.hooks.run("complete", s),
r && r.call(s.element);
a.hooks.run('after-highlight', s),
a.hooks.run('complete', s),
r && r.call(s.element)
}
if (
(a.hooks.run("before-sanity-check", s),
(a.hooks.run('before-sanity-check', s),
(o = s.element.parentElement) &&
"pre" === o.nodeName.toLowerCase() &&
!o.hasAttribute("tabindex") &&
o.setAttribute("tabindex", "0"),
'pre' === o.nodeName.toLowerCase() &&
!o.hasAttribute('tabindex') &&
o.setAttribute('tabindex', '0'),
!s.code)
)
return a.hooks.run("complete", s), void (r && r.call(s.element));
if ((a.hooks.run("before-highlight", s), s.grammar))
return a.hooks.run('complete', s), void (r && r.call(s.element))
if ((a.hooks.run('before-highlight', s), s.grammar))
if (t && e.Worker) {
var c = new Worker(a.filename);
(c.onmessage = function (e) {
u(e.data);
var c = new Worker(a.filename)
;(c.onmessage = function (e) {
u(e.data)
}),
c.postMessage(
JSON.stringify({
language: s.language,
code: s.code,
immediateClose: !0,
}),
);
} else u(a.highlight(s.code, s.grammar, s.language));
else u(a.util.encode(s.code));
})
)
} else u(a.highlight(s.code, s.grammar, s.language))
else u(a.util.encode(s.code))
},
highlight: function (e, n, t) {
var r = { code: e, grammar: n, language: t };
if ((a.hooks.run("before-tokenize", r), !r.grammar))
throw new Error(
'The language "' + r.language + '" has no grammar.',
);
var r = { code: e, grammar: n, language: t }
if ((a.hooks.run('before-tokenize', r), !r.grammar))
throw new Error('The language "' + r.language + '" has no grammar.')
return (
(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)
);
)
},
tokenize: function (e, n) {
var t = n.rest;
var t = n.rest
if (t) {
for (var r in t) n[r] = t[r];
delete n.rest;
for (var r in t) n[r] = t[r]
delete n.rest
}
var a = new s();
var a = new s()
return (
u(a, a.head, e),
o(e, a, n, a.head, 0),
(function (e) {
for (var n = [], t = e.head.next; t !== e.tail; )
n.push(t.value), (t = t.next);
return n;
n.push(t.value), (t = t.next)
return n
})(a)
);
)
},
hooks: {
all: {},
add: function (e, n) {
var t = a.hooks.all;
(t[e] = t[e] || []), t[e].push(n);
var t = a.hooks.all
;(t[e] = t[e] || []), t[e].push(n)
},
run: function (e, n) {
var t = a.hooks.all[e];
if (t && t.length) for (var r, i = 0; (r = t[i++]); ) r(n);
var t = a.hooks.all[e]
if (t && t.length) for (var r, i = 0; (r = t[i++]); ) r(n)
},
},
Token: i,
};
}
function i(e, n, t, r) {
(this.type = e),
;(this.type = e),
(this.content = n),
(this.alias = t),
(this.length = 0 | (r || "").length);
(this.length = 0 | (r || '').length)
}
function l(e, n, t, r) {
e.lastIndex = n;
var a = e.exec(t);
e.lastIndex = n
var a = e.exec(t)
if (a && r && a[1]) {
var i = a[1].length;
(a.index += i), (a[0] = a[0].slice(i));
var i = a[1].length
;(a.index += i), (a[0] = a[0].slice(i))
}
return a;
return a
}
function o(e, n, t, r, s, g) {
for (var f in t)
if (t.hasOwnProperty(f) && t[f]) {
var h = t[f];
h = Array.isArray(h) ? h : [h];
var h = t[f]
h = Array.isArray(h) ? h : [h]
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],
p = v.inside,
m = !!v.lookbehind,
y = !!v.greedy,
k = v.alias;
k = v.alias
if (y && !v.pattern.global) {
var x = v.pattern.toString().match(/[imsuy]*$/)[0];
v.pattern = RegExp(v.pattern.source, x + "g");
var x = v.pattern.toString().match(/[imsuy]*$/)[0]
v.pattern = RegExp(v.pattern.source, x + 'g')
}
for (
var b = v.pattern || v, w = r.next, A = s;
w !== n.tail && !(g && A >= g.reach);
A += w.value.length, w = w.next
) {
var E = w.value;
if (n.length > e.length) return;
var E = w.value
if (n.length > e.length) return
if (!(E instanceof i)) {
var P,
L = 1;
L = 1
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,
O = P.index + P[0].length,
j = A;
j = A
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))
continue;
continue
for (
var C = w;
C !== n.tail && (j < O || "string" == typeof C.value);
C !== n.tail && (j < O || 'string' == typeof C.value);
C = C.next
)
L++, (j += C.value.length);
L--, (E = e.slice(A, j)), (P.index -= A);
} else if (!(P = l(b, 0, E, m))) continue;
S = P.index;
L++, (j += C.value.length)
L--, (E = e.slice(A, j)), (P.index -= A)
} else if (!(P = l(b, 0, E, m))) continue
S = P.index
var N = P[0],
_ = E.slice(0, S),
M = E.slice(S + N.length),
W = A + E.length;
g && W > g.reach && (g.reach = W);
var z = w.prev;
W = A + E.length
g && W > g.reach && (g.reach = W)
var z = w.prev
if (
(_ && ((z = u(n, z, _)), (A += _.length)),
c(n, z, L),
@ -310,9 +308,9 @@ var _self =
M && u(n, w, M),
L > 1)
) {
var I = { cause: f + "," + d, reach: W };
var I = { cause: f + ',' + d, reach: W }
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() {
var e = { value: null, prev: null, next: null },
n = { value: null, prev: e, next: null };
(e.next = n), (this.head = e), (this.tail = n), (this.length = 0);
n = { value: null, prev: e, next: null }
;(e.next = n), (this.head = e), (this.tail = n), (this.length = 0)
}
function u(e, n, t) {
var r = n.next,
a = { value: t, prev: n, next: r };
return (n.next = a), (r.prev = a), e.length++, a;
a = { value: t, prev: n, next: r }
return (n.next = a), (r.prev = a), e.length++, a
}
function c(e, n, t) {
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);
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)
}
if (
((e.Prism = a),
(i.stringify = function e(n, t) {
if ("string" == typeof n) return n;
if ('string' == typeof n) return n
if (Array.isArray(n)) {
var r = "";
var r = ''
return (
n.forEach(function (n) {
r += e(n, t);
r += e(n, t)
}),
r
);
)
}
var i = {
type: n.type,
content: e(n.content, t),
tag: "span",
classes: ["token", n.type],
tag: 'span',
classes: ['token', n.type],
attributes: {},
language: t,
},
l = n.alias;
l = n.alias
l &&
(Array.isArray(l)
? Array.prototype.push.apply(i.classes, l)
: i.classes.push(l)),
a.hooks.run("wrap", i);
var o = "";
a.hooks.run('wrap', i)
var o = ''
for (var s in i.attributes)
o +=
" " +
' ' +
s +
'="' +
(i.attributes[s] || "").replace(/"/g, "&quot;") +
'"';
(i.attributes[s] || '').replace(/"/g, '&quot;') +
'"'
return (
"<" +
'<' +
i.tag +
' class="' +
i.classes.join(" ") +
i.classes.join(' ') +
'"' +
o +
">" +
'>' +
i.content +
"</" +
'</' +
i.tag +
">"
);
'>'
)
}),
!e.document)
)
return e.addEventListener
? (a.disableWorkerMessageHandler ||
e.addEventListener(
"message",
'message',
function (n) {
var t = JSON.parse(n.data),
r = t.language,
i = t.code,
l = t.immediateClose;
e.postMessage(a.highlight(i, a.languages[r], r)),
l && e.close();
l = t.immediateClose
e.postMessage(a.highlight(i, a.languages[r], r)), l && e.close()
},
!1,
!1
),
a)
: a;
var g = a.util.currentScript();
: a
var g = a.util.currentScript()
function f() {
a.manual || a.highlightAll();
a.manual || a.highlightAll()
}
if (
(g &&
((a.filename = g.src),
g.hasAttribute("data-manual") && (a.manual = !0)),
g.hasAttribute('data-manual') && (a.manual = !0)),
!a.manual)
) {
var h = document.readyState;
"loading" === h || ("interactive" === h && g && g.defer)
? document.addEventListener("DOMContentLoaded", f)
var h = document.readyState
'loading' === h || ('interactive' === h && g && g.defer)
? document.addEventListener('DOMContentLoaded', f)
: window.requestAnimationFrame
? window.requestAnimationFrame(f)
: window.setTimeout(f, 16);
: window.setTimeout(f, 16)
}
return a;
})(_self);
"undefined" != typeof module && module.exports && (module.exports = Prism),
"undefined" != typeof global && (global.Prism = Prism);
(Prism.languages.markup = {
return a
})(_self)
'undefined' != typeof module && module.exports && (module.exports = Prism),
'undefined' != typeof global && (global.Prism = Prism)
;(Prism.languages.markup = {
comment: { pattern: /<!--(?:(?!<!--)[\s\S])*?-->/, greedy: !0 },
prolog: { pattern: /<\?[\s\S]+?\?>/, greedy: !0 },
doctype: {
@ -429,7 +426,7 @@ var _self =
/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,
greedy: !0,
inside: {
"internal-subset": {
'internal-subset': {
pattern: /(^[^\[]*\[)[\s\S]+(?=\]>$)/,
lookbehind: !0,
greedy: !0,
@ -437,7 +434,7 @@ var _self =
},
string: { pattern: /"[^"]*"|'[^']*'/, greedy: !0 },
punctuation: /^<!|>$|[[\]]/,
"doctype-tag": /^DOCTYPE/i,
'doctype-tag': /^DOCTYPE/i,
name: /[^\s<>'"]+/,
},
},
@ -451,120 +448,118 @@ var _self =
pattern: /^<\/?[^\s>\/]+/,
inside: { punctuation: /^<\/?/, namespace: /^[^\s>\/:]+:/ },
},
"special-attr": [],
"attr-value": {
'special-attr': [],
'attr-value': {
pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,
inside: {
punctuation: [
{ pattern: /^=/, alias: "attr-equals" },
{ pattern: /^=/, alias: 'attr-equals' },
{ pattern: /^(\s*)["']|["']$/, lookbehind: !0 },
],
},
},
punctuation: /\/?>/,
"attr-name": {
'attr-name': {
pattern: /[^\s>\/]+/,
inside: { namespace: /^[^\s>\/:]+:/ },
},
},
},
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,
],
}),
(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.doctype.inside["internal-subset"].inside =
(Prism.languages.markup.doctype.inside['internal-subset'].inside =
Prism.languages.markup),
Prism.hooks.add("wrap", function (a) {
"entity" === a.type &&
(a.attributes.title = a.content.replace(/&amp;/, "&"));
Prism.hooks.add('wrap', function (a) {
'entity' === a.type &&
(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) {
var s = {};
(s["language-" + e] = {
var s = {}
;(s['language-' + e] = {
pattern: /(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,
lookbehind: !0,
inside: Prism.languages[e],
}),
(s.cdata = /^<!\[CDATA\[|\]\]>$/i);
(s.cdata = /^<!\[CDATA\[|\]\]>$/i)
var t = {
"included-cdata": { pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i, inside: s },
};
t["language-" + e] = { pattern: /[\s\S]+/, inside: Prism.languages[e] };
var n = {};
(n[a] = {
'included-cdata': { pattern: /<!\[CDATA\[[\s\S]*?\]\]>/i, inside: s },
}
t['language-' + e] = { pattern: /[\s\S]+/, inside: Prism.languages[e] }
var n = {}
;(n[a] = {
pattern: RegExp(
"(<__[^>]*>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)".replace(
'(<__[^>]*>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)'.replace(
/__/g,
function () {
return a;
},
return a
}
),
"i",
'i'
),
lookbehind: !0,
greedy: !0,
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) {
Prism.languages.markup.tag.inside["special-attr"].push({
Prism.languages.markup.tag.inside['special-attr'].push({
pattern: RegExp(
"(^|[\"'\\s])(?:" +
'(^|["\'\\s])(?:' +
a +
")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))",
"i",
')\\s*=\\s*(?:"[^"]*"|\'[^\']*\'|[^\\s\'">=]+(?=[\\s>]))',
'i'
),
lookbehind: !0,
inside: {
"attr-name": /^[^\s=]+/,
"attr-value": {
'attr-name': /^[^\s=]+/,
'attr-value': {
pattern: /=[\s\S]+/,
inside: {
value: {
pattern: /(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,
lookbehind: !0,
alias: [e, "language-" + e],
alias: [e, 'language-' + e],
inside: Prism.languages[e],
},
punctuation: [{ pattern: /^=/, alias: "attr-equals" }, /"|'/],
punctuation: [{ pattern: /^=/, alias: 'attr-equals' }, /"|'/],
},
},
},
});
})
},
}),
(Prism.languages.html = Prism.languages.markup),
(Prism.languages.mathml = 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.atom = Prism.languages.xml),
(Prism.languages.rss = Prism.languages.xml);
(Prism.languages.rss = Prism.languages.xml)
!(function (s) {
var e =
/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;
(s.languages.css = {
/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/
;(s.languages.css = {
comment: /\/\*[\s\S]*?\*\//,
atrule: {
pattern: RegExp(
"@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|" +
e.source +
")*?(?:;|(?=\\s*\\{))",
'@[\\w-](?:[^;{\\s"\']|\\s+(?!\\s)|' + e.source + ')*?(?:;|(?=\\s*\\{))'
),
inside: {
rule: /^@[\w-]+/,
"selector-function-argument": {
'selector-function-argument': {
pattern:
/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,
lookbehind: !0,
alias: "selector",
alias: 'selector',
},
keyword: {
pattern: /(^|[^\w-])(?:and|not|only|or)(?![\w-])/,
@ -574,21 +569,21 @@ var _self =
},
url: {
pattern: RegExp(
"\\burl\\((?:" + e.source + "|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)",
"i",
'\\burl\\((?:' + e.source + '|(?:[^\\\\\r\n()"\']|\\\\[^])*)\\)',
'i'
),
greedy: !0,
inside: {
function: /^url/i,
punctuation: /^\(|\)$/,
string: { pattern: RegExp("^" + e.source + "$"), alias: "url" },
string: { pattern: RegExp('^' + e.source + '$'), alias: 'url' },
},
},
selector: {
pattern: RegExp(
"(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|" +
'(^|[{}\\s])[^{}\\s](?:[^{};"\'\\s]|\\s+(?![\\s{])|' +
e.source +
")*(?=\\s*\\{)",
')*(?=\\s*\\{)'
),
lookbehind: !0,
},
@ -602,10 +597,10 @@ var _self =
function: { pattern: /(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i, lookbehind: !0 },
punctuation: /[(){};:,]/,
}),
(s.languages.css.atrule.inside.rest = s.languages.css);
var t = s.languages.markup;
t && (t.tag.addInlined("style", "css"), t.tag.addAttribute("style", "css"));
})(Prism);
(s.languages.css.atrule.inside.rest = s.languages.css)
var t = s.languages.markup
t && (t.tag.addInlined('style', 'css'), t.tag.addAttribute('style', 'css'))
})(Prism)
Prism.languages.clike = {
comment: [
{ pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/, lookbehind: !0, greedy: !0 },
@ -615,7 +610,7 @@ Prism.languages.clike = {
pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,
greedy: !0,
},
"class-name": {
'class-name': {
pattern:
/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,
lookbehind: !0,
@ -628,10 +623,10 @@ Prism.languages.clike = {
number: /\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,
operator: /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,
punctuation: /[{}[\];(),.:]/,
};
(Prism.languages.javascript = Prism.languages.extend("clike", {
"class-name": [
Prism.languages.clike["class-name"],
}
;(Prism.languages.javascript = Prism.languages.extend('clike', {
'class-name': [
Prism.languages.clike['class-name'],
{
pattern:
/(^|[^$\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*)?\()/,
number: {
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,
},
operator:
/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/,
})),
(Prism.languages.javascript["class-name"][0].pattern =
(Prism.languages.javascript['class-name'][0].pattern =
/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/),
Prism.languages.insertBefore("javascript", "keyword", {
Prism.languages.insertBefore('javascript', 'keyword', {
regex: {
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,
greedy: !0,
inside: {
"regex-source": {
'regex-source': {
pattern: /^(\/)[\s\S]+(?=\/[a-z]*$)/,
lookbehind: !0,
alias: "language-regex",
alias: 'language-regex',
inside: Prism.languages.regex,
},
"regex-delimiter": /^\/|\/$/,
"regex-flags": /^[a-z]+$/,
'regex-delimiter': /^\/|\/$/,
'regex-flags': /^[a-z]+$/,
},
},
"function-variable": {
'function-variable': {
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*=>))/,
alias: "function",
alias: 'function',
},
parameter: [
{
@ -710,22 +705,22 @@ Prism.languages.clike = {
],
constant: /\b[A-Z](?:[A-Z_]|\dx?)*\b/,
}),
Prism.languages.insertBefore("javascript", "string", {
hashbang: { pattern: /^#!.*/, greedy: !0, alias: "comment" },
"template-string": {
Prism.languages.insertBefore('javascript', 'string', {
hashbang: { pattern: /^#!.*/, greedy: !0, alias: 'comment' },
'template-string': {
pattern:
/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,
greedy: !0,
inside: {
"template-punctuation": { pattern: /^`|`$/, alias: "string" },
'template-punctuation': { pattern: /^`|`$/, alias: 'string' },
interpolation: {
pattern:
/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,
lookbehind: !0,
inside: {
"interpolation-punctuation": {
'interpolation-punctuation': {
pattern: /^\$\{|\}$/,
alias: "punctuation",
alias: 'punctuation',
},
rest: Prism.languages.javascript,
},
@ -733,26 +728,26 @@ Prism.languages.clike = {
string: /[\s\S]+/,
},
},
"string-property": {
'string-property': {
pattern:
/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,
lookbehind: !0,
greedy: !0,
alias: "property",
alias: 'property',
},
}),
Prism.languages.insertBefore("javascript", "operator", {
"literal-property": {
Prism.languages.insertBefore('javascript', 'operator', {
'literal-property': {
pattern:
/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,
lookbehind: !0,
alias: "property",
alias: 'property',
},
}),
Prism.languages.markup &&
(Prism.languages.markup.tag.addInlined("script", "javascript"),
(Prism.languages.markup.tag.addInlined('script', 'javascript'),
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)",
"javascript",
'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'
)),
(Prism.languages.js = Prism.languages.javascript);
(Prism.languages.js = Prism.languages.javascript)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,15 +6,15 @@ const htm =
l,
s = arguments,
t = 1,
u = "",
r = "",
u = '',
r = '',
o = [0],
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)
: 3 === t && (n || u)
? ((o[1] = n ? s[n] : u), (t = 2))
: 2 === t && "..." === u && n
: 2 === t && '...' === u && n
? (o[2] = Object.assign(o[2] || {}, s[n]))
: 2 === t && u && !n
? ((o[2] = o[2] || {})[u] = !0)
@ -27,50 +27,50 @@ const htm =
: u),
(t = 6))
: (n || u) && (o[2][l] += n ? u + s[n] : u)),
(u = "");
(u = '')
},
i = 0;
i < n.length;
i++
) {
i && (1 === t && f(), f(i));
i && (1 === t && f(), f(i))
for (var p = 0; p < n[i].length; p++)
(e = n[i][p]),
1 === t
? "<" === e
? (f(), (o = [o, "", null]), (t = 3))
? '<' === e
? (f(), (o = [o, '', null]), (t = 3))
: (u += e)
: 4 === t
? "--" === u && ">" === e
? ((t = 1), (u = ""))
? '--' === u && '>' === e
? ((t = 1), (u = ''))
: (u = e + u[0])
: r
? e === r
? (r = "")
? (r = '')
: (u += e)
: '"' === e || "'" === e
? (r = e)
: ">" === e
: '>' === e
? (f(), (t = 1))
: t &&
("=" === e
? ((t = 5), (l = u), (u = ""))
: "/" === e && (t < 5 || ">" === n[i][p + 1])
('=' === e
? ((t = 5), (l = u), (u = ''))
: '/' === e && (t < 5 || '>' === n[i][p + 1])
? (f(),
3 === t && (o = o[0]),
(t = o),
(o = o[0]).push(this.apply(null, t.slice(1))),
(t = 0))
: " " === e || "\t" === e || "\n" === e || "\r" === e
: ' ' === e || '\t' === e || '\n' === e || '\r' === e
? (f(), (t = 2))
: (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) {
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>
* @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 { WebComponent } from "./WebComponent.js";
export { html } from "./html.js";
export { attachEffect } from './attach-effect.js'
export { WebComponent } from './WebComponent.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) {
if (!tree.type) {
if (Array.isArray(tree)) {
const frag = document.createDocumentFragment();
frag.replaceChildren(...tree.map((leaf) => createElement(leaf)));
return frag;
const frag = document.createDocumentFragment()
frag.replaceChildren(...tree.map((leaf) => createElement(leaf)))
return frag
}
return document.createTextNode(tree);
return document.createTextNode(tree)
} else {
const el = document.createElement(tree.type);
const el = document.createElement(tree.type)
/**
* handle props
*/
if (tree.props) {
Object.entries(tree.props).forEach(([prop, value]) => {
const domProp = prop.toLowerCase();
if (domProp === "style" && typeof value === "object" && !!value) {
applyStyles(el, value);
const domProp = prop.toLowerCase()
if (domProp === 'style' && typeof value === 'object' && !!value) {
applyStyles(el, value)
} else if (prop in el) {
el[prop] = value;
el[prop] = value
} else if (domProp in el) {
el[domProp] = value;
el[domProp] = value
} else {
el.setAttribute(prop, serialize(value));
el.setAttribute(prop, serialize(value))
}
});
})
}
/**
* handle children
*/
tree.children?.forEach((child) => {
const childEl = createElement(child);
const childEl = createElement(child)
if (childEl instanceof Node) {
el.appendChild(childEl);
el.appendChild(childEl)
}
});
return el;
})
return el
}
}
function applyStyles(el, styleObj) {
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) {
switch (type) {
case "number":
case "boolean":
case "object":
case "undefined":
return JSON.parse(value);
case 'number':
case 'boolean':
case 'object':
case 'undefined':
return JSON.parse(value)
default:
return value;
return value
}
}

View file

@ -1,3 +1,3 @@
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) {
return str.replace(
/[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 { deserialize } from "./deserialize.mjs";
export { getCamelCase } from "./get-camel-case.mjs";
export { getKebabCase } from "./get-kebab-case.mjs";
export { createElement } from "./create-element.mjs";
export { serialize } from './serialize.mjs'
export { deserialize } from './deserialize.mjs'
export { getCamelCase } from './get-camel-case.mjs'
export { getKebabCase } from './get-kebab-case.mjs'
export { createElement } from './create-element.mjs'

View file

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

View file

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