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.
Files changed (69) hide show
  1. package/.eslintrc.json +46 -0
  2. package/.github/workflows/ci.yml +49 -0
  3. package/.prettierrc +7 -0
  4. package/LICENSE +20 -0
  5. package/README.md +270 -35
  6. package/demos/counter/counter.element.html +1 -0
  7. package/demos/counter/counter.element.scss +234 -0
  8. package/demos/counter/counter.element.ts +68 -0
  9. package/demos/counter/index.html +205 -0
  10. package/demos/counter/index.ts +1 -0
  11. package/demos/site/code-example/code-example.element.scss +168 -0
  12. package/demos/site/code-example/code-example.element.ts +88 -0
  13. package/demos/site/event-log/event-log.element.scss +179 -0
  14. package/demos/site/event-log/event-log.element.ts +134 -0
  15. package/demos/site/favicon.svg +14 -0
  16. package/demos/site/index.html +346 -0
  17. package/demos/site/index.ts +13 -0
  18. package/demos/site/message/message.element.scss +75 -0
  19. package/demos/site/message/message.element.ts +76 -0
  20. package/demos/site/og-image.png +0 -0
  21. package/demos/site/styles/site.css +1023 -0
  22. package/demos/site/styles/tokens.css +56 -0
  23. package/demos/site/toast/toast.element.scss +110 -0
  24. package/demos/site/toast/toast.element.ts +63 -0
  25. package/demos/todo-dashboard/index.html +141 -0
  26. package/demos/todo-dashboard/index.ts +4 -0
  27. package/demos/todo-dashboard/todo-dashboard.element.scss +1145 -0
  28. package/demos/todo-dashboard/todo-dashboard.element.ts +332 -0
  29. package/demos/todo-dashboard/todo-filters.element.ts +54 -0
  30. package/demos/todo-dashboard/todo-item.element.ts +126 -0
  31. package/demos/todo-dashboard/todo-stats.element.ts +189 -0
  32. package/package.json +73 -24
  33. package/src/custom-element.ts +206 -0
  34. package/{esm5/index.d.ts → src/index.ts} +7 -5
  35. package/src/listen.ts +70 -0
  36. package/src/prop.ts +92 -0
  37. package/src/state.ts +129 -0
  38. package/src/template-runtime.ts +435 -0
  39. package/src/toggle.ts +66 -0
  40. package/src/tsconfig.json +24 -0
  41. package/src/util.ts +33 -0
  42. package/src/watch.ts +14 -0
  43. package/tests/basic.spec.ts +70 -0
  44. package/tests/custom-element.spec.ts +77 -0
  45. package/tests/dispatch.spec.ts +52 -0
  46. package/tests/init.spec.ts +94 -0
  47. package/tests/listen.spec.ts +118 -0
  48. package/tests/prop.spec.ts +118 -0
  49. package/tests/templating-runtime.spec.ts +575 -0
  50. package/tests/toggle.spec.ts +92 -0
  51. package/tests/watch.spec.ts +183 -0
  52. package/tools/build.js +119 -0
  53. package/tools/bundle.js +167 -0
  54. package/tools/rollup-config.js +70 -0
  55. package/tools/start.js +188 -0
  56. package/tsconfig.json +38 -0
  57. package/vite.config.mts +30 -0
  58. package/bundles/custom-elements-ts.umd.js +0 -359
  59. package/bundles/custom-elements-ts.umd.js.map +0 -1
  60. package/esm2015/custom-elements-ts.js +0 -283
  61. package/esm2015/custom-elements-ts.js.map +0 -1
  62. package/esm5/custom-element.d.ts +0 -12
  63. package/esm5/custom-elements-ts.js +0 -344
  64. package/esm5/custom-elements-ts.js.map +0 -1
  65. package/esm5/listen.d.ts +0 -17
  66. package/esm5/prop.d.ts +0 -2
  67. package/esm5/toggle.d.ts +0 -1
  68. package/esm5/util.d.ts +0 -4
  69. 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
@@ -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
@@ -5,46 +5,180 @@
5
5
  [![npm version](https://badge.fury.io/js/custom-elements-ts.svg)](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
7
 
8
-
9
- Create native custom elements using Typescript without using any third party libraries.
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
- ## Usage
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 { CustomElement } from 'custom-elements-ts';
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
- // code as you would when creating a native HTMLElement
27
- // full source code is at demo/counter
118
+ // Wire up DOM manually in connectedCallback / @Watch / @Listen.
28
119
  }
29
120
  ```
30
121
 
31
- ```html
32
- <!--index.html-->
33
- <counter-element></counter-element>
34
- <script src="counter.umd.js"></script>
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, 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 |
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
- Since `color` is a primitive type of `string` it can be accessed via attributes and properties
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
- 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.
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
- 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:
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, Dispatch, Prop } from 'custom-elements-ts';
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 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)
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, Dispatch, Prop } from 'custom-elements-ts';
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
- ## Setup
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
- ### Running the demos
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
- ### Building the demo
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
- If you want to create a minified bundle
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 with typings):
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>