custom-elements-ts 0.0.17 → 0.2.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.
Files changed (82) hide show
  1. package/.eslintrc.json +47 -0
  2. package/.github/workflows/ci.yml +49 -0
  3. package/.prettierrc +7 -0
  4. package/LICENSE +20 -0
  5. package/README.md +279 -37
  6. package/assets/readme-header.png +0 -0
  7. package/assets/social-preview.jpg +0 -0
  8. package/demos/counter/counter.element.html +1 -0
  9. package/demos/counter/counter.element.scss +234 -0
  10. package/demos/counter/counter.element.ts +68 -0
  11. package/demos/counter/index.html +205 -0
  12. package/demos/counter/index.ts +1 -0
  13. package/demos/site/code-example/code-example.element.scss +168 -0
  14. package/demos/site/code-example/code-example.element.ts +88 -0
  15. package/demos/site/event-log/event-log.element.scss +179 -0
  16. package/demos/site/event-log/event-log.element.ts +123 -0
  17. package/demos/site/favicon.svg +14 -0
  18. package/demos/site/index.html +368 -0
  19. package/demos/site/index.ts +19 -0
  20. package/demos/site/llms.txt +53 -0
  21. package/demos/site/message/message.element.scss +75 -0
  22. package/demos/site/message/message.element.ts +76 -0
  23. package/demos/site/og-image.png +0 -0
  24. package/demos/site/scroll-restoration.ts +28 -0
  25. package/demos/site/source-toggle/source-toggle.ts +88 -0
  26. package/demos/site/source-viewer/source-viewer.element.scss +292 -0
  27. package/demos/site/source-viewer/source-viewer.element.ts +138 -0
  28. package/demos/site/source-viewer/sources.generated.ts +11 -0
  29. package/demos/site/styles/site.css +1135 -0
  30. package/demos/site/styles/tokens.css +56 -0
  31. package/demos/site/toast/toast.element.scss +110 -0
  32. package/demos/site/toast/toast.element.ts +63 -0
  33. package/demos/todo-dashboard/index.html +141 -0
  34. package/demos/todo-dashboard/index.ts +4 -0
  35. package/demos/todo-dashboard/todo-dashboard.element.scss +1150 -0
  36. package/demos/todo-dashboard/todo-dashboard.element.ts +333 -0
  37. package/demos/todo-dashboard/todo-filters.element.ts +54 -0
  38. package/demos/todo-dashboard/todo-item.element.ts +127 -0
  39. package/demos/todo-dashboard/todo-stats.element.ts +189 -0
  40. package/package.json +73 -24
  41. package/src/custom-element.ts +206 -0
  42. package/src/index.ts +8 -0
  43. package/src/listen.ts +70 -0
  44. package/src/prop.ts +92 -0
  45. package/src/signal.ts +66 -0
  46. package/src/state.ts +141 -0
  47. package/src/template-runtime.ts +783 -0
  48. package/src/toggle.ts +66 -0
  49. package/src/tsconfig.json +24 -0
  50. package/src/util.ts +33 -0
  51. package/src/watch.ts +14 -0
  52. package/tests/basic.spec.ts +70 -0
  53. package/tests/custom-element.spec.ts +77 -0
  54. package/tests/dispatch.spec.ts +52 -0
  55. package/tests/init.spec.ts +94 -0
  56. package/tests/listen.spec.ts +118 -0
  57. package/tests/map-shallow-state.spec.ts +184 -0
  58. package/tests/prop.spec.ts +118 -0
  59. package/tests/signal.spec.ts +117 -0
  60. package/tests/templating-runtime.spec.ts +575 -0
  61. package/tests/toggle.spec.ts +92 -0
  62. package/tests/watch.spec.ts +183 -0
  63. package/tools/build.js +128 -0
  64. package/tools/bundle.js +167 -0
  65. package/tools/generate-sources.js +76 -0
  66. package/tools/rollup-config.js +70 -0
  67. package/tools/start.js +194 -0
  68. package/tsconfig.json +38 -0
  69. package/vite.config.mts +30 -0
  70. package/bundles/custom-elements-ts.umd.js +0 -359
  71. package/bundles/custom-elements-ts.umd.js.map +0 -1
  72. package/esm2015/custom-elements-ts.js +0 -283
  73. package/esm2015/custom-elements-ts.js.map +0 -1
  74. package/esm5/custom-element.d.ts +0 -12
  75. package/esm5/custom-elements-ts.js +0 -344
  76. package/esm5/custom-elements-ts.js.map +0 -1
  77. package/esm5/index.d.ts +0 -5
  78. package/esm5/listen.d.ts +0 -17
  79. package/esm5/prop.d.ts +0 -2
  80. package/esm5/toggle.d.ts +0 -1
  81. package/esm5/util.d.ts +0 -4
  82. package/esm5/watch.d.ts +0 -1
package/.eslintrc.json ADDED
@@ -0,0 +1,47 @@
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-require-imports": "off",
24
+ "@typescript-eslint/no-unused-vars": "off",
25
+ "no-undef": "off",
26
+ "no-console": "off",
27
+ "prettier/prettier": "off"
28
+ }
29
+ }
30
+ ],
31
+ "ignorePatterns": ["dist/", ".tmp/", "node_modules/", "demos/**"],
32
+ "rules": {
33
+ "eqeqeq": ["error", "always"],
34
+ "no-throw-literal": "error",
35
+ "prettier/prettier": "error",
36
+ "@typescript-eslint/explicit-function-return-type": "off",
37
+ "@typescript-eslint/no-explicit-any": "off",
38
+ "@typescript-eslint/no-unused-vars": [
39
+ "warn",
40
+ {
41
+ "args": "none",
42
+ "varsIgnorePattern": "^_|^[A-Z].*Element$",
43
+ "caughtErrorsIgnorePattern": "^_"
44
+ }
45
+ ]
46
+ }
47
+ }
@@ -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
@@ -0,0 +1,7 @@
1
+ {
2
+ "singleQuote": true,
3
+ "semi": true,
4
+ "trailingComma": "es5",
5
+ "printWidth": 100,
6
+ "endOfLine": "auto"
7
+ }
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
@@ -1,50 +1,191 @@
1
1
  # custom-elements-ts
2
2
 
3
- [![Coverage Status](https://coveralls.io/repos/github/geocine/custom-elements-ts/badge.svg?branch=master)](https://coveralls.io/github/geocine/custom-elements-ts?branch=master)
4
- ![CI](https://github.com/geocine/custom-elements-ts/actions/workflows/ci.yml/badge.svg)
5
- [![npm version](https://badge.fury.io/js/custom-elements-ts.svg)](https://www.npmjs.com/package/custom-elements-ts)
3
+ [![Coverage Status](https://coveralls.io/repos/github/geocine/custom-elements-ts/badge.svg?branch=master&cache-control=no-cache)](https://coveralls.io/github/geocine/custom-elements-ts?branch=master)
4
+ ![CI](https://github.com/geocine/custom-elements-ts/actions/workflows/ci.yml/badge.svg?cache-control=no-cache)
5
+ [![npm version](https://badge.fury.io/js/custom-elements-ts.svg?cache-control=no-cache)](https://www.npmjs.com/package/custom-elements-ts)
6
6
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/geocine/custom-elements-ts)
7
8
 
9
+ <p align="center">
10
+ <a href="https://geocine.github.io/custom-elements-ts/">
11
+ <img src="assets/readme-header.png" alt="custom-elements-ts — Native Web Components, written in TypeScript">
12
+ </a>
13
+ </p>
8
14
 
9
- Create native custom elements using Typescript without using any third party libraries.
15
+ Author native Web Components in TypeScript with a small set of decorators
16
+ (`@CustomElement`, `@Prop`, `@State`, `@Watch`, `@Listen`, `@Dispatch`,
17
+ `@Toggle`) plus a tiny `html` / `render()` runtime. **Zero dependencies.
18
+ Framework-free.**
10
19
 
11
20
  ```
12
21
  npm install custom-elements-ts
13
22
  ```
14
23
 
15
- ## Usage
24
+ > **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.
25
+
26
+ ## Table of contents
27
+
28
+ - [Quick start](#quick-start)
29
+ - [Plain HTML — no `render()` required](#plain-html--no-render-required)
30
+ - [Reactive components with `render()`](#reactive-components-with-render)
31
+ - [Template bindings](#template-bindings)
32
+ - [Decorators](#decorators)
33
+ - [@Prop()](#prop)
34
+ - [@State()](#state)
35
+ - [@Toggle()](#toggle)
36
+ - [@Dispatch()](#dispatch)
37
+ - [@Watch()](#watch)
38
+ - [@Listen()](#listen)
39
+ - [Project layout](#project-layout)
40
+ - [Running the demos](#running-the-demos)
41
+ - [Building](#building)
42
+
43
+ ## Quick start
44
+
45
+ There are **two ways** to author a component, and they compose freely:
46
+
47
+ 1. **Plain HTML, imperative updates.** Declare a `template` (or
48
+ `templateUrl`) and update the DOM yourself in `connectedCallback`,
49
+ `@Watch()` handlers, or `@Listen()` handlers. No `render()`. No
50
+ reactive runtime. **Use this when the DOM is mostly static** —
51
+ buttons, badges, panels, copy-to-clipboard pills, and so on.
52
+ 2. **Reactive `render()` with the `html` helper.** Define `render()` and
53
+ the runtime re-renders for you on `@Prop()` / `@State()` / `@Toggle()`
54
+ changes. Use this for stateful components like dashboards, counters,
55
+ forms, and lists.
56
+
57
+ `render()` is **optional** — components without it pay zero runtime
58
+ cost beyond the decorators themselves.
59
+
60
+ ### Plain HTML — no `render()` required
61
+
62
+ A small toast-firing "click to copy" pill, written entirely with a
63
+ static template and imperative DOM. This is exactly the pattern used by
64
+ `<cts-message>` on the [showcase page](https://geocine.github.io/custom-elements-ts/):
16
65
 
17
66
  ```ts
18
- import { CustomElement } from 'custom-elements-ts';
67
+ import {
68
+ CustomElement,
69
+ Prop,
70
+ Listen,
71
+ Dispatch,
72
+ DispatchEmitter,
73
+ } from 'custom-elements-ts';
19
74
 
75
+ @CustomElement({
76
+ tag: 'cts-message',
77
+ template: `
78
+ <div class="row" role="button" tabindex="0">
79
+ <span class="prompt">$</span>
80
+ <code class="cmd"></code>
81
+ </div>
82
+ `,
83
+ styleUrl: './message.element.scss',
84
+ })
85
+ export class MessageElement extends HTMLElement {
86
+ @Prop() message!: string;
87
+
88
+ // Bubbling, composed CustomEvent — any ancestor can listen for it
89
+ // (e.g. a <cts-toast> at the document root).
90
+ @Dispatch('cts:toast') toast!: DispatchEmitter;
91
+
92
+ connectedCallback() {
93
+ // Imperative DOM update — no render() needed.
94
+ this.shadowRoot!.querySelector('.cmd')!.textContent = this.message;
95
+ }
96
+
97
+ @Listen('click')
98
+ async handleClick() {
99
+ await navigator.clipboard.writeText(this.message);
100
+ this.toast.emit({
101
+ bubbles: true,
102
+ composed: true,
103
+ detail: { title: 'Copied to clipboard', message: this.message },
104
+ });
105
+ }
106
+ }
107
+ ```
108
+
109
+ ```html
110
+ <!-- Drop it anywhere — React, Vue, Svelte, plain HTML — it just works -->
111
+ <cts-message message="npm install custom-elements-ts"></cts-message>
112
+ <script src="message.umd.js"></script>
113
+ ```
114
+
115
+ You can also keep markup in its own file with `templateUrl` and
116
+ `styleUrl`, exactly as you would with any other framework:
117
+
118
+ ```ts
20
119
  @CustomElement({
21
120
  tag: 'counter-element',
22
- templateUrl: 'counter-element.html',
23
- styleUrl: 'counter-element.scss'
121
+ templateUrl: './counter-element.html',
122
+ styleUrl: './counter-element.scss',
24
123
  })
25
124
  export class CounterElement extends HTMLElement {
26
- // code as you would when creating a native HTMLElement
27
- // full source code is at demo/counter
125
+ // Wire up DOM manually in connectedCallback / @Watch / @Listen.
28
126
  }
29
127
  ```
30
128
 
31
- ```html
32
- <!--index.html-->
33
- <counter-element></counter-element>
34
- <script src="counter.umd.js"></script>
129
+ ### Reactive components with `render()`
130
+
131
+ Add a `render()` method that returns an `html` template literal and the
132
+ runtime takes care of efficient DOM updates whenever any
133
+ `@Prop()` / `@State()` / `@Toggle()` value changes:
134
+
135
+ ```ts
136
+ import { CustomElement, State, html } from 'custom-elements-ts';
137
+
138
+ @CustomElement({ tag: 'cts-counter' })
139
+ export class CounterElement extends HTMLElement {
140
+ @State() count = 0;
141
+
142
+ render() {
143
+ return html`<button @click=${this.increment}>Count: ${this.count}</button>`;
144
+ }
145
+
146
+ private increment() {
147
+ this.count++;
148
+ }
149
+ }
150
+ ```
151
+
152
+ `render()` output is mounted into the shadow root by default. Pass
153
+ `shadow: false` to render into the host element instead.
154
+
155
+ ### Template bindings
156
+
157
+ When you do opt into `render()`, the `html` helper supports the common
158
+ binding forms used by render-based components:
159
+
160
+ ```ts
161
+ html`<p>${this.label}</p>`;
162
+ html`<p>${() => this.label}</p>`;
163
+ html`<button @click=${this.handleClick}></button>`;
164
+ html`<input .value=${this.value} />`;
165
+ html`<div title=${this.title}></div>`;
166
+ html`<ul>
167
+ ${this.items.map((item) => html`<li>${item.label}</li>`)}
168
+ </ul>`;
35
169
  ```
36
170
 
171
+ Attribute bindings remove the attribute when the value is `false`,
172
+ `null`, or `undefined`. Event bindings replace old listeners when a
173
+ render supplies a new handler and are cleaned up automatically when the
174
+ rendered template is disposed.
175
+
37
176
  ## Decorators
38
177
 
39
178
  | Decorator | Target | Parameters | Description |
40
- |-------------|----------|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
41
- | @Prop() | property | - | custom attribute/properties, reflects primitive properties (string, number, boolean) to attributes |
42
- | @Toggle() | property | - | boolean attribute/properties, it is based on the presence of the attribute but also works with "true" and "false" |
43
- | @Dispatch() | property | (event?) | used to declare a CustomEvent which you could dispatch using the `.emit` method of its type `DispatchEmitter`. The `event` parameter is used to set the name of the `CustomEvent` |
44
- | @Watch() | method | (property) | triggers the method when a `property` is changed |
45
- | @Listen() | method | (event, selector?) | listens to an `event` on the `host` element or on the `selector` if specified |
179
+ | ----------- | -------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
180
+ | @Prop() | property | - | custom attribute/properties; reflects primitive values (string, number, boolean) to attributes |
181
+ | @State() | property | - | private reactive state for render-based components; not reflected to attributes |
182
+ | @Toggle() | property | - | boolean attribute/properties based on the presence of the attribute; also accepts `"true"` and `"false"` |
183
+ | @Dispatch() | property | (event?) | declares a `CustomEvent` you can fire via the `.emit` method of its `DispatchEmitter` type. The `event` parameter sets the `CustomEvent` name |
184
+ | @Watch() | method | (property) | runs the method when `property` changes |
185
+ | @Listen() | method | (event, selector?) | listens for `event` on the host (or on `selector` inside the shadow tree) |
46
186
 
47
187
  ### @Prop()
188
+
48
189
  ```ts
49
190
  import { CustomElement, Prop } from 'custom-elements-ts';
50
191
 
@@ -57,21 +198,26 @@ export class TodoList extends HTMLElement {
57
198
  @Prop() list: TodoItem[];
58
199
  }
59
200
  ```
60
- Since `color` is a primitive type of `string` it can be accessed via attributes and properties
201
+
202
+ Since `color` is a primitive type of `string` it can be accessed via
203
+ attributes and properties:
204
+
61
205
  ```ts
62
206
  const element = document.querySelector('todo-list');
63
207
  // accessing value via attribute
64
208
  const attrValue = element.getAttribute('color');
65
209
  // setting value via attribute
66
210
  element.setAttribute('color', 'red');
67
-
211
+
68
212
  // accessing value via property
69
213
  const propertyValue = element.color;
70
214
  // setting via property
71
215
  element.color = 'red';
72
216
  ```
73
217
 
74
- On the other hand `list` is a rich data type (objects or arrays), and functions/classes can only be accessed/set via property and are not reflected as attributes.
218
+ `list` is a rich data type (objects or arrays) and functions/classes can
219
+ only be accessed/set via property — they are not reflected as
220
+ attributes:
75
221
 
76
222
  ```ts
77
223
  // Functions and classes are not reflected to attributes
@@ -88,11 +234,55 @@ element.itemConstructor = Foo;
88
234
  console.log(element.getAttribute('item-ctor')); // null
89
235
  ```
90
236
 
237
+ Render-based components update after a real `@Prop()` value change.
238
+ Multiple prop and state changes inside the same synchronous turn are
239
+ batched into one render.
240
+
241
+ ### @State()
242
+
243
+ ```ts
244
+ import { CustomElement, State, Watch, html } from 'custom-elements-ts';
245
+
246
+ @CustomElement({ tag: 'profile-card' })
247
+ export class ProfileCard extends HTMLElement {
248
+ @State() user = { name: 'Ada' };
249
+ @State() items = [{ label: 'One' }];
250
+
251
+ @Watch('user')
252
+ userChanged(value: { old: unknown; new: unknown }) {
253
+ console.log(value.new);
254
+ }
255
+
256
+ render() {
257
+ return html`
258
+ <strong>${this.user.name}</strong>
259
+ <ul>
260
+ ${this.items.map((item) => html`<li>${item.label}</li>`)}
261
+ </ul>
262
+ `;
263
+ }
264
+ }
265
+ ```
266
+
267
+ State is internal to the element: it is not reflected to attributes and
268
+ is not included in `observedAttributes`. Plain objects and arrays
269
+ assigned to state are deeply proxied, so nested mutations such as
270
+ `this.user.name = 'Grace'`, `this.items.push(...)`, and
271
+ `this.items[0].label = 'Updated'` schedule a render.
272
+
273
+ Only plain objects and arrays are proxied. Functions, class
274
+ constructors, DOM nodes, `Date`, `Map`, `Set`, `WeakMap`, and `WeakSet`
275
+ are left as-is — reassign those values to trigger a render.
276
+
91
277
  ### @Toggle()
92
- Toggle attributes work the same way as HTML boolean attributes as defined by [W3C](http://www.w3.org/TR/2008/WD-html5-20080610/semantics.html#boolean) for the most part. We changed a few things to overcome confusion. Check the table below for reference:
278
+
279
+ Toggle attributes work the same way as HTML boolean attributes as
280
+ defined by [W3C](http://www.w3.org/TR/2008/WD-html5-20080610/semantics.html#boolean)
281
+ for the most part. We changed a few things to overcome confusion. Check
282
+ the table below for reference:
93
283
 
94
284
  | Markup | `disabled` | Description |
95
- |-------------------------------|------------|----------------------------------------------------------------------|
285
+ | ----------------------------- | ---------- | -------------------------------------------------------------------- |
96
286
  | `<c-input />` | false | Follows W3C standard |
97
287
  | `<c-input disabled/>` | true | Follows W3C standard |
98
288
  | `<c-input disabled="true"/>` | true | Follows W3C standard |
@@ -113,23 +303,37 @@ export class TodoList extends HTMLElement {
113
303
  // custom event name will be `on.change`
114
304
  @Dispatch() onChange: DispatchEmitter;
115
305
 
116
- // Creating a CustomEvent with custom name `ce.select`
306
+ // Creating a CustomEvent with custom name `ce.select`
117
307
  @Dispatch('ce.select') onSelect: DispatchEmitter;
118
308
  }
119
309
  ```
310
+
120
311
  **Triggering the custom event** from the example above:
121
312
 
122
313
  ```ts
123
314
  triggerOnChange() {
124
315
  // adding more data to the event object
125
- this.onChange.emit({detail: 'event changed'});
126
- this.onSelect.emit({detail: 'select triggered'});
316
+ this.onChange.emit({ detail: 'event changed' });
317
+ this.onSelect.emit({ detail: 'select triggered' });
127
318
  }
128
319
  ```
320
+
321
+ For events that need to cross the shadow boundary (e.g. so a parent or
322
+ the document can listen) opt into bubbling and composed delivery on the
323
+ `emit()` call:
324
+
325
+ ```ts
326
+ this.onChange.emit({
327
+ bubbles: true,
328
+ composed: true,
329
+ detail: { count: this.count },
330
+ });
331
+ ```
332
+
129
333
  ### @Watch()
130
334
 
131
335
  ```ts
132
- import { CustomElement, Dispatch, Prop } from 'custom-elements-ts';
336
+ import { CustomElement, Prop, Watch } from 'custom-elements-ts';
133
337
 
134
338
  ...
135
339
  export class TodoList extends HTMLElement {
@@ -145,10 +349,12 @@ export class TodoList extends HTMLElement {
145
349
 
146
350
  ### @Listen()
147
351
 
148
- Listen has parameters `event` and `selector`. `Event` is any valid javascript event. `Selector` is anything that works with [querySelector()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)
352
+ `@Listen()` takes an `event` and an optional `selector`. `event` is any
353
+ valid JavaScript event. `selector` is anything that works with
354
+ [`querySelector()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector).
149
355
 
150
356
  ```ts
151
- import { CustomElement, Dispatch, Prop } from 'custom-elements-ts';
357
+ import { CustomElement, Listen } from 'custom-elements-ts';
152
358
 
153
359
  ...
154
360
  export class TodoList extends HTMLElement {
@@ -157,34 +363,70 @@ export class TodoList extends HTMLElement {
157
363
  // triggers when the element is clicked
158
364
  }
159
365
 
160
- @Listen('click','a')
366
+ @Listen('click', 'a')
161
367
  anchorClicked() {
162
368
  // triggers when an `a` inside the element is clicked
163
369
  }
164
370
  }
165
371
  ```
166
372
 
167
- ## Setup
373
+ ## Project layout
374
+
375
+ ```
376
+ src/ # the library — decorators + html/render runtime
377
+ demos/
378
+ counter/ # @State() + render() — single counter card
379
+ todo-dashboard/ # composed elements: stats, filters, items, parent
380
+ site/ # the showcase landing page that hosts every live demo
381
+ tests/ # vitest specs for the runtime + decorators
382
+ tools/ # build / start / bundle scripts
383
+ ```
168
384
 
169
- ### Running the demos
385
+ The site demo (`demos/site`) imports the counter and the todo-dashboard
386
+ elements from sibling demo folders, so the showcase page on
387
+ `localhost:3000` runs the **real** components — not screenshots — and
388
+ includes a `<cts-event-log>` panel that subscribes to their bubbling
389
+ `CustomEvent`s in real time.
390
+
391
+ ## Running the demos
170
392
 
171
393
  ```
172
394
  npm start <element-name>
173
395
  ```
174
396
 
175
- ### Building the demo
397
+ | Element | Highlights |
398
+ | ---------------- | ---------------------------------------------------------------- |
399
+ | `site` | Showcase landing page (hero, code preview, live demos, OG graph) |
400
+ | `counter` | `@State()` + `@Watch()` + `@Dispatch()` on a single card |
401
+ | `todo-dashboard` | Parent / child composition with deeply proxied state |
402
+
403
+ ```
404
+ npm start site
405
+ npm start counter
406
+ npm start todo-dashboard
407
+ ```
408
+
409
+ The dev server runs on `http://localhost:3000` and live-reloads on
410
+ TypeScript / SCSS / HTML changes.
411
+
412
+ ## Building
413
+
414
+ ### Building a demo
176
415
 
177
416
  ```
178
417
  npm run build <element-name>
179
418
  ```
180
- If you want to create a minified bundle
419
+
420
+ For a minified bundle:
421
+
181
422
  ```
182
423
  npm run build -- <element-name> --prod
183
424
  ```
184
425
 
185
426
  ### Building the library (publish artifacts)
186
427
 
187
- Builds the library from `src/index.ts` into `dist/` (UMD + ESM builds with typings):
428
+ Builds the library from `src/index.ts` into `dist/` (UMD + ESM builds
429
+ with typings):
188
430
 
189
431
  ```
190
432
  npm run bundle
Binary file
Binary file
@@ -0,0 +1 @@
1
+ <button id="count"></button>