custom-elements-ts 0.0.17 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +46 -0
- package/.github/workflows/ci.yml +49 -0
- package/.prettierrc +7 -0
- package/LICENSE +20 -0
- package/README.md +270 -35
- package/demos/counter/counter.element.html +1 -0
- package/demos/counter/counter.element.scss +234 -0
- package/demos/counter/counter.element.ts +68 -0
- package/demos/counter/index.html +205 -0
- package/demos/counter/index.ts +1 -0
- package/demos/site/code-example/code-example.element.scss +168 -0
- package/demos/site/code-example/code-example.element.ts +88 -0
- package/demos/site/event-log/event-log.element.scss +179 -0
- package/demos/site/event-log/event-log.element.ts +134 -0
- package/demos/site/favicon.svg +14 -0
- package/demos/site/index.html +346 -0
- package/demos/site/index.ts +13 -0
- package/demos/site/message/message.element.scss +75 -0
- package/demos/site/message/message.element.ts +76 -0
- package/demos/site/og-image.png +0 -0
- package/demos/site/styles/site.css +1023 -0
- package/demos/site/styles/tokens.css +56 -0
- package/demos/site/toast/toast.element.scss +110 -0
- package/demos/site/toast/toast.element.ts +63 -0
- package/demos/todo-dashboard/index.html +141 -0
- package/demos/todo-dashboard/index.ts +4 -0
- package/demos/todo-dashboard/todo-dashboard.element.scss +1145 -0
- package/demos/todo-dashboard/todo-dashboard.element.ts +332 -0
- package/demos/todo-dashboard/todo-filters.element.ts +54 -0
- package/demos/todo-dashboard/todo-item.element.ts +126 -0
- package/demos/todo-dashboard/todo-stats.element.ts +189 -0
- package/package.json +73 -24
- package/src/custom-element.ts +206 -0
- package/{esm5/index.d.ts → src/index.ts} +7 -5
- package/src/listen.ts +70 -0
- package/src/prop.ts +92 -0
- package/src/state.ts +129 -0
- package/src/template-runtime.ts +435 -0
- package/src/toggle.ts +66 -0
- package/src/tsconfig.json +24 -0
- package/src/util.ts +33 -0
- package/src/watch.ts +14 -0
- package/tests/basic.spec.ts +70 -0
- package/tests/custom-element.spec.ts +77 -0
- package/tests/dispatch.spec.ts +52 -0
- package/tests/init.spec.ts +94 -0
- package/tests/listen.spec.ts +118 -0
- package/tests/prop.spec.ts +118 -0
- package/tests/templating-runtime.spec.ts +575 -0
- package/tests/toggle.spec.ts +92 -0
- package/tests/watch.spec.ts +183 -0
- package/tools/build.js +119 -0
- package/tools/bundle.js +167 -0
- package/tools/rollup-config.js +70 -0
- package/tools/start.js +188 -0
- package/tsconfig.json +38 -0
- package/vite.config.mts +30 -0
- package/bundles/custom-elements-ts.umd.js +0 -359
- package/bundles/custom-elements-ts.umd.js.map +0 -1
- package/esm2015/custom-elements-ts.js +0 -283
- package/esm2015/custom-elements-ts.js.map +0 -1
- package/esm5/custom-element.d.ts +0 -12
- package/esm5/custom-elements-ts.js +0 -344
- package/esm5/custom-elements-ts.js.map +0 -1
- package/esm5/listen.d.ts +0 -17
- package/esm5/prop.d.ts +0 -2
- package/esm5/toggle.d.ts +0 -1
- package/esm5/util.d.ts +0 -4
- package/esm5/watch.d.ts +0 -1
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"root": true,
|
|
3
|
+
"parser": "@typescript-eslint/parser",
|
|
4
|
+
"parserOptions": {
|
|
5
|
+
"ecmaVersion": 2019,
|
|
6
|
+
"sourceType": "module",
|
|
7
|
+
"warnOnUnsupportedTypeScriptVersion": false
|
|
8
|
+
},
|
|
9
|
+
"plugins": ["@typescript-eslint", "prettier"],
|
|
10
|
+
"extends": [
|
|
11
|
+
"eslint:recommended",
|
|
12
|
+
"plugin:@typescript-eslint/recommended",
|
|
13
|
+
"plugin:prettier/recommended"
|
|
14
|
+
],
|
|
15
|
+
"env": { "browser": true, "es6": true, "jasmine": true },
|
|
16
|
+
"overrides": [
|
|
17
|
+
{
|
|
18
|
+
"files": ["tools/**/*.js"],
|
|
19
|
+
"parser": "espree",
|
|
20
|
+
"env": { "node": true, "es6": true },
|
|
21
|
+
"rules": {
|
|
22
|
+
"@typescript-eslint/no-var-requires": "off",
|
|
23
|
+
"@typescript-eslint/no-unused-vars": "off",
|
|
24
|
+
"no-undef": "off",
|
|
25
|
+
"no-console": "off",
|
|
26
|
+
"prettier/prettier": "off"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"ignorePatterns": ["dist/", ".tmp/", "node_modules/", "demos/**"],
|
|
31
|
+
"rules": {
|
|
32
|
+
"eqeqeq": ["error", "always"],
|
|
33
|
+
"no-throw-literal": "error",
|
|
34
|
+
"prettier/prettier": "error",
|
|
35
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
36
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
37
|
+
"@typescript-eslint/no-unused-vars": [
|
|
38
|
+
"warn",
|
|
39
|
+
{
|
|
40
|
+
"args": "none",
|
|
41
|
+
"varsIgnorePattern": "^_|^[A-Z].*Element$",
|
|
42
|
+
"caughtErrorsIgnorePattern": "^_"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["master"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: ["master"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build-test-deploy:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Use Node.js 20
|
|
19
|
+
uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: 20
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: npm ci
|
|
25
|
+
|
|
26
|
+
- name: Run tests with coverage
|
|
27
|
+
run: npm run test:coverage
|
|
28
|
+
|
|
29
|
+
- name: Upload coverage to Coveralls
|
|
30
|
+
uses: coverallsapp/github-action@v2
|
|
31
|
+
with:
|
|
32
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
33
|
+
path-to-lcov: ./coverage/lcov.info
|
|
34
|
+
|
|
35
|
+
- name: Build demo site bundle
|
|
36
|
+
run: npm run build site
|
|
37
|
+
|
|
38
|
+
# The build emits index.html, site.umd.js, and styles/* into dist/.
|
|
39
|
+
# All asset URLs in index.html are relative (no <base> tag), so they
|
|
40
|
+
# resolve correctly under the GitHub Pages project subpath
|
|
41
|
+
# (e.g. https://geocine.github.io/custom-elements-ts/) without any
|
|
42
|
+
# post-processing. No path rewriting is required here.
|
|
43
|
+
|
|
44
|
+
- name: Deploy to GitHub Pages
|
|
45
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
|
46
|
+
uses: peaceiris/actions-gh-pages@v3
|
|
47
|
+
with:
|
|
48
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
49
|
+
publish_dir: ./dist
|
package/.prettierrc
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018-present Aivan Monceller
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -5,46 +5,180 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/custom-elements-ts)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
Author native Web Components in TypeScript with a small set of decorators
|
|
9
|
+
(`@CustomElement`, `@Prop`, `@State`, `@Watch`, `@Listen`, `@Dispatch`,
|
|
10
|
+
`@Toggle`) plus a tiny `html` / `render()` runtime. **Zero dependencies.
|
|
11
|
+
Framework-free.**
|
|
10
12
|
|
|
11
13
|
```
|
|
12
14
|
npm install custom-elements-ts
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
> **Live demos:** [geocine.github.io/custom-elements-ts](https://geocine.github.io/custom-elements-ts/) — counter, sprint board, install pill, and a live event log, all built with the library.
|
|
18
|
+
|
|
19
|
+
## Table of contents
|
|
20
|
+
|
|
21
|
+
- [Quick start](#quick-start)
|
|
22
|
+
- [Plain HTML — no `render()` required](#plain-html--no-render-required)
|
|
23
|
+
- [Reactive components with `render()`](#reactive-components-with-render)
|
|
24
|
+
- [Template bindings](#template-bindings)
|
|
25
|
+
- [Decorators](#decorators)
|
|
26
|
+
- [@Prop()](#prop)
|
|
27
|
+
- [@State()](#state)
|
|
28
|
+
- [@Toggle()](#toggle)
|
|
29
|
+
- [@Dispatch()](#dispatch)
|
|
30
|
+
- [@Watch()](#watch)
|
|
31
|
+
- [@Listen()](#listen)
|
|
32
|
+
- [Project layout](#project-layout)
|
|
33
|
+
- [Running the demos](#running-the-demos)
|
|
34
|
+
- [Building](#building)
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
There are **two ways** to author a component, and they compose freely:
|
|
39
|
+
|
|
40
|
+
1. **Plain HTML, imperative updates.** Declare a `template` (or
|
|
41
|
+
`templateUrl`) and update the DOM yourself in `connectedCallback`,
|
|
42
|
+
`@Watch()` handlers, or `@Listen()` handlers. No `render()`. No
|
|
43
|
+
reactive runtime. **Use this when the DOM is mostly static** —
|
|
44
|
+
buttons, badges, panels, copy-to-clipboard pills, and so on.
|
|
45
|
+
2. **Reactive `render()` with the `html` helper.** Define `render()` and
|
|
46
|
+
the runtime re-renders for you on `@Prop()` / `@State()` / `@Toggle()`
|
|
47
|
+
changes. Use this for stateful components like dashboards, counters,
|
|
48
|
+
forms, and lists.
|
|
49
|
+
|
|
50
|
+
`render()` is **optional** — components without it pay zero runtime
|
|
51
|
+
cost beyond the decorators themselves.
|
|
52
|
+
|
|
53
|
+
### Plain HTML — no `render()` required
|
|
54
|
+
|
|
55
|
+
A small toast-firing "click to copy" pill, written entirely with a
|
|
56
|
+
static template and imperative DOM. This is exactly the pattern used by
|
|
57
|
+
`<cts-message>` on the [showcase page](https://geocine.github.io/custom-elements-ts/):
|
|
16
58
|
|
|
17
59
|
```ts
|
|
18
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
CustomElement,
|
|
62
|
+
Prop,
|
|
63
|
+
Listen,
|
|
64
|
+
Dispatch,
|
|
65
|
+
DispatchEmitter,
|
|
66
|
+
} from 'custom-elements-ts';
|
|
67
|
+
|
|
68
|
+
@CustomElement({
|
|
69
|
+
tag: 'cts-message',
|
|
70
|
+
template: `
|
|
71
|
+
<div class="row" role="button" tabindex="0">
|
|
72
|
+
<span class="prompt">$</span>
|
|
73
|
+
<code class="cmd"></code>
|
|
74
|
+
</div>
|
|
75
|
+
`,
|
|
76
|
+
styleUrl: './message.element.scss',
|
|
77
|
+
})
|
|
78
|
+
export class MessageElement extends HTMLElement {
|
|
79
|
+
@Prop() message!: string;
|
|
80
|
+
|
|
81
|
+
// Bubbling, composed CustomEvent — any ancestor can listen for it
|
|
82
|
+
// (e.g. a <cts-toast> at the document root).
|
|
83
|
+
@Dispatch('cts:toast') toast!: DispatchEmitter;
|
|
84
|
+
|
|
85
|
+
connectedCallback() {
|
|
86
|
+
// Imperative DOM update — no render() needed.
|
|
87
|
+
this.shadowRoot!.querySelector('.cmd')!.textContent = this.message;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@Listen('click')
|
|
91
|
+
async handleClick() {
|
|
92
|
+
await navigator.clipboard.writeText(this.message);
|
|
93
|
+
this.toast.emit({
|
|
94
|
+
bubbles: true,
|
|
95
|
+
composed: true,
|
|
96
|
+
detail: { title: 'Copied to clipboard', message: this.message },
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
19
101
|
|
|
102
|
+
```html
|
|
103
|
+
<!-- Drop it anywhere — React, Vue, Svelte, plain HTML — it just works -->
|
|
104
|
+
<cts-message message="npm install custom-elements-ts"></cts-message>
|
|
105
|
+
<script src="message.umd.js"></script>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
You can also keep markup in its own file with `templateUrl` and
|
|
109
|
+
`styleUrl`, exactly as you would with any other framework:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
20
112
|
@CustomElement({
|
|
21
113
|
tag: 'counter-element',
|
|
22
|
-
templateUrl: 'counter-element.html',
|
|
23
|
-
styleUrl: 'counter-element.scss'
|
|
114
|
+
templateUrl: './counter-element.html',
|
|
115
|
+
styleUrl: './counter-element.scss',
|
|
24
116
|
})
|
|
25
117
|
export class CounterElement extends HTMLElement {
|
|
26
|
-
//
|
|
27
|
-
// full source code is at demo/counter
|
|
118
|
+
// Wire up DOM manually in connectedCallback / @Watch / @Listen.
|
|
28
119
|
}
|
|
29
120
|
```
|
|
30
121
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
122
|
+
### Reactive components with `render()`
|
|
123
|
+
|
|
124
|
+
Add a `render()` method that returns an `html` template literal and the
|
|
125
|
+
runtime takes care of efficient DOM updates whenever any
|
|
126
|
+
`@Prop()` / `@State()` / `@Toggle()` value changes:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { CustomElement, State, html } from 'custom-elements-ts';
|
|
130
|
+
|
|
131
|
+
@CustomElement({ tag: 'cts-counter' })
|
|
132
|
+
export class CounterElement extends HTMLElement {
|
|
133
|
+
@State() count = 0;
|
|
134
|
+
|
|
135
|
+
render() {
|
|
136
|
+
return html`<button @click=${this.increment}>Count: ${this.count}</button>`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private increment() {
|
|
140
|
+
this.count++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
35
143
|
```
|
|
36
144
|
|
|
145
|
+
`render()` output is mounted into the shadow root by default. Pass
|
|
146
|
+
`shadow: false` to render into the host element instead.
|
|
147
|
+
|
|
148
|
+
### Template bindings
|
|
149
|
+
|
|
150
|
+
When you do opt into `render()`, the `html` helper supports the common
|
|
151
|
+
binding forms used by render-based components:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
html`<p>${this.label}</p>`;
|
|
155
|
+
html`<p>${() => this.label}</p>`;
|
|
156
|
+
html`<button @click=${this.handleClick}></button>`;
|
|
157
|
+
html`<input .value=${this.value} />`;
|
|
158
|
+
html`<div title=${this.title}></div>`;
|
|
159
|
+
html`<ul>
|
|
160
|
+
${this.items.map((item) => html`<li>${item.label}</li>`)}
|
|
161
|
+
</ul>`;
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Attribute bindings remove the attribute when the value is `false`,
|
|
165
|
+
`null`, or `undefined`. Event bindings replace old listeners when a
|
|
166
|
+
render supplies a new handler and are cleaned up automatically when the
|
|
167
|
+
rendered template is disposed.
|
|
168
|
+
|
|
37
169
|
## Decorators
|
|
38
170
|
|
|
39
171
|
| Decorator | Target | Parameters | Description |
|
|
40
|
-
|
|
41
|
-
| @Prop() | property | - | custom attribute/properties
|
|
42
|
-
| @
|
|
43
|
-
| @
|
|
44
|
-
| @
|
|
45
|
-
| @
|
|
172
|
+
| ----------- | -------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
173
|
+
| @Prop() | property | - | custom attribute/properties; reflects primitive values (string, number, boolean) to attributes |
|
|
174
|
+
| @State() | property | - | private reactive state for render-based components; not reflected to attributes |
|
|
175
|
+
| @Toggle() | property | - | boolean attribute/properties based on the presence of the attribute; also accepts `"true"` and `"false"` |
|
|
176
|
+
| @Dispatch() | property | (event?) | declares a `CustomEvent` you can fire via the `.emit` method of its `DispatchEmitter` type. The `event` parameter sets the `CustomEvent` name |
|
|
177
|
+
| @Watch() | method | (property) | runs the method when `property` changes |
|
|
178
|
+
| @Listen() | method | (event, selector?) | listens for `event` on the host (or on `selector` inside the shadow tree) |
|
|
46
179
|
|
|
47
180
|
### @Prop()
|
|
181
|
+
|
|
48
182
|
```ts
|
|
49
183
|
import { CustomElement, Prop } from 'custom-elements-ts';
|
|
50
184
|
|
|
@@ -57,21 +191,26 @@ export class TodoList extends HTMLElement {
|
|
|
57
191
|
@Prop() list: TodoItem[];
|
|
58
192
|
}
|
|
59
193
|
```
|
|
60
|
-
|
|
194
|
+
|
|
195
|
+
Since `color` is a primitive type of `string` it can be accessed via
|
|
196
|
+
attributes and properties:
|
|
197
|
+
|
|
61
198
|
```ts
|
|
62
199
|
const element = document.querySelector('todo-list');
|
|
63
200
|
// accessing value via attribute
|
|
64
201
|
const attrValue = element.getAttribute('color');
|
|
65
202
|
// setting value via attribute
|
|
66
203
|
element.setAttribute('color', 'red');
|
|
67
|
-
|
|
204
|
+
|
|
68
205
|
// accessing value via property
|
|
69
206
|
const propertyValue = element.color;
|
|
70
207
|
// setting via property
|
|
71
208
|
element.color = 'red';
|
|
72
209
|
```
|
|
73
210
|
|
|
74
|
-
|
|
211
|
+
`list` is a rich data type (objects or arrays) and functions/classes can
|
|
212
|
+
only be accessed/set via property — they are not reflected as
|
|
213
|
+
attributes:
|
|
75
214
|
|
|
76
215
|
```ts
|
|
77
216
|
// Functions and classes are not reflected to attributes
|
|
@@ -88,11 +227,55 @@ element.itemConstructor = Foo;
|
|
|
88
227
|
console.log(element.getAttribute('item-ctor')); // null
|
|
89
228
|
```
|
|
90
229
|
|
|
230
|
+
Render-based components update after a real `@Prop()` value change.
|
|
231
|
+
Multiple prop and state changes inside the same synchronous turn are
|
|
232
|
+
batched into one render.
|
|
233
|
+
|
|
234
|
+
### @State()
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { CustomElement, State, Watch, html } from 'custom-elements-ts';
|
|
238
|
+
|
|
239
|
+
@CustomElement({ tag: 'profile-card' })
|
|
240
|
+
export class ProfileCard extends HTMLElement {
|
|
241
|
+
@State() user = { name: 'Ada' };
|
|
242
|
+
@State() items = [{ label: 'One' }];
|
|
243
|
+
|
|
244
|
+
@Watch('user')
|
|
245
|
+
userChanged(value: { old: unknown; new: unknown }) {
|
|
246
|
+
console.log(value.new);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
render() {
|
|
250
|
+
return html`
|
|
251
|
+
<strong>${this.user.name}</strong>
|
|
252
|
+
<ul>
|
|
253
|
+
${this.items.map((item) => html`<li>${item.label}</li>`)}
|
|
254
|
+
</ul>
|
|
255
|
+
`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
State is internal to the element: it is not reflected to attributes and
|
|
261
|
+
is not included in `observedAttributes`. Plain objects and arrays
|
|
262
|
+
assigned to state are deeply proxied, so nested mutations such as
|
|
263
|
+
`this.user.name = 'Grace'`, `this.items.push(...)`, and
|
|
264
|
+
`this.items[0].label = 'Updated'` schedule a render.
|
|
265
|
+
|
|
266
|
+
Only plain objects and arrays are proxied. Functions, class
|
|
267
|
+
constructors, DOM nodes, `Date`, `Map`, `Set`, `WeakMap`, and `WeakSet`
|
|
268
|
+
are left as-is — reassign those values to trigger a render.
|
|
269
|
+
|
|
91
270
|
### @Toggle()
|
|
92
|
-
|
|
271
|
+
|
|
272
|
+
Toggle attributes work the same way as HTML boolean attributes as
|
|
273
|
+
defined by [W3C](http://www.w3.org/TR/2008/WD-html5-20080610/semantics.html#boolean)
|
|
274
|
+
for the most part. We changed a few things to overcome confusion. Check
|
|
275
|
+
the table below for reference:
|
|
93
276
|
|
|
94
277
|
| Markup | `disabled` | Description |
|
|
95
|
-
|
|
278
|
+
| ----------------------------- | ---------- | -------------------------------------------------------------------- |
|
|
96
279
|
| `<c-input />` | false | Follows W3C standard |
|
|
97
280
|
| `<c-input disabled/>` | true | Follows W3C standard |
|
|
98
281
|
| `<c-input disabled="true"/>` | true | Follows W3C standard |
|
|
@@ -113,23 +296,37 @@ export class TodoList extends HTMLElement {
|
|
|
113
296
|
// custom event name will be `on.change`
|
|
114
297
|
@Dispatch() onChange: DispatchEmitter;
|
|
115
298
|
|
|
116
|
-
// Creating a CustomEvent with custom name `ce.select`
|
|
299
|
+
// Creating a CustomEvent with custom name `ce.select`
|
|
117
300
|
@Dispatch('ce.select') onSelect: DispatchEmitter;
|
|
118
301
|
}
|
|
119
302
|
```
|
|
303
|
+
|
|
120
304
|
**Triggering the custom event** from the example above:
|
|
121
305
|
|
|
122
306
|
```ts
|
|
123
307
|
triggerOnChange() {
|
|
124
308
|
// adding more data to the event object
|
|
125
|
-
this.onChange.emit({detail: 'event changed'});
|
|
126
|
-
this.onSelect.emit({detail: 'select triggered'});
|
|
309
|
+
this.onChange.emit({ detail: 'event changed' });
|
|
310
|
+
this.onSelect.emit({ detail: 'select triggered' });
|
|
127
311
|
}
|
|
128
312
|
```
|
|
313
|
+
|
|
314
|
+
For events that need to cross the shadow boundary (e.g. so a parent or
|
|
315
|
+
the document can listen) opt into bubbling and composed delivery on the
|
|
316
|
+
`emit()` call:
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
this.onChange.emit({
|
|
320
|
+
bubbles: true,
|
|
321
|
+
composed: true,
|
|
322
|
+
detail: { count: this.count },
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
129
326
|
### @Watch()
|
|
130
327
|
|
|
131
328
|
```ts
|
|
132
|
-
import { CustomElement,
|
|
329
|
+
import { CustomElement, Prop, Watch } from 'custom-elements-ts';
|
|
133
330
|
|
|
134
331
|
...
|
|
135
332
|
export class TodoList extends HTMLElement {
|
|
@@ -145,10 +342,12 @@ export class TodoList extends HTMLElement {
|
|
|
145
342
|
|
|
146
343
|
### @Listen()
|
|
147
344
|
|
|
148
|
-
Listen
|
|
345
|
+
`@Listen()` takes an `event` and an optional `selector`. `event` is any
|
|
346
|
+
valid JavaScript event. `selector` is anything that works with
|
|
347
|
+
[`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector).
|
|
149
348
|
|
|
150
349
|
```ts
|
|
151
|
-
import { CustomElement,
|
|
350
|
+
import { CustomElement, Listen } from 'custom-elements-ts';
|
|
152
351
|
|
|
153
352
|
...
|
|
154
353
|
export class TodoList extends HTMLElement {
|
|
@@ -157,34 +356,70 @@ export class TodoList extends HTMLElement {
|
|
|
157
356
|
// triggers when the element is clicked
|
|
158
357
|
}
|
|
159
358
|
|
|
160
|
-
@Listen('click','a')
|
|
359
|
+
@Listen('click', 'a')
|
|
161
360
|
anchorClicked() {
|
|
162
361
|
// triggers when an `a` inside the element is clicked
|
|
163
362
|
}
|
|
164
363
|
}
|
|
165
364
|
```
|
|
166
365
|
|
|
167
|
-
##
|
|
366
|
+
## Project layout
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
src/ # the library — decorators + html/render runtime
|
|
370
|
+
demos/
|
|
371
|
+
counter/ # @State() + render() — single counter card
|
|
372
|
+
todo-dashboard/ # composed elements: stats, filters, items, parent
|
|
373
|
+
site/ # the showcase landing page that hosts every live demo
|
|
374
|
+
tests/ # vitest specs for the runtime + decorators
|
|
375
|
+
tools/ # build / start / bundle scripts
|
|
376
|
+
```
|
|
168
377
|
|
|
169
|
-
|
|
378
|
+
The site demo (`demos/site`) imports the counter and the todo-dashboard
|
|
379
|
+
elements from sibling demo folders, so the showcase page on
|
|
380
|
+
`localhost:3000` runs the **real** components — not screenshots — and
|
|
381
|
+
includes a `<cts-event-log>` panel that subscribes to their bubbling
|
|
382
|
+
`CustomEvent`s in real time.
|
|
383
|
+
|
|
384
|
+
## Running the demos
|
|
170
385
|
|
|
171
386
|
```
|
|
172
387
|
npm start <element-name>
|
|
173
388
|
```
|
|
174
389
|
|
|
175
|
-
|
|
390
|
+
| Element | Highlights |
|
|
391
|
+
| ---------------- | ---------------------------------------------------------------- |
|
|
392
|
+
| `site` | Showcase landing page (hero, code preview, live demos, OG graph) |
|
|
393
|
+
| `counter` | `@State()` + `@Watch()` + `@Dispatch()` on a single card |
|
|
394
|
+
| `todo-dashboard` | Parent / child composition with deeply proxied state |
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
npm start site
|
|
398
|
+
npm start counter
|
|
399
|
+
npm start todo-dashboard
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
The dev server runs on `http://localhost:3000` and live-reloads on
|
|
403
|
+
TypeScript / SCSS / HTML changes.
|
|
404
|
+
|
|
405
|
+
## Building
|
|
406
|
+
|
|
407
|
+
### Building a demo
|
|
176
408
|
|
|
177
409
|
```
|
|
178
410
|
npm run build <element-name>
|
|
179
411
|
```
|
|
180
|
-
|
|
412
|
+
|
|
413
|
+
For a minified bundle:
|
|
414
|
+
|
|
181
415
|
```
|
|
182
416
|
npm run build -- <element-name> --prod
|
|
183
417
|
```
|
|
184
418
|
|
|
185
419
|
### Building the library (publish artifacts)
|
|
186
420
|
|
|
187
|
-
Builds the library from `src/index.ts` into `dist/` (UMD + ESM builds
|
|
421
|
+
Builds the library from `src/index.ts` into `dist/` (UMD + ESM builds
|
|
422
|
+
with typings):
|
|
188
423
|
|
|
189
424
|
```
|
|
190
425
|
npm run bundle
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<button id="count"></button>
|