snice 2.5.4 → 3.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/README.md +501 -882
- package/bin/templates/base/src/components/counter-button.ts +13 -26
- package/bin/templates/base/src/controllers/counter-controller.ts +3 -3
- package/dist/components/accordion/snice-accordion-item.d.ts +4 -5
- package/dist/components/accordion/snice-accordion-item.js +37 -39
- package/dist/components/accordion/snice-accordion-item.js.map +1 -1
- package/dist/components/accordion/snice-accordion.d.ts +5 -11
- package/dist/components/accordion/snice-accordion.js +51 -52
- package/dist/components/accordion/snice-accordion.js.map +1 -1
- package/dist/components/alert/snice-alert.d.ts +2 -6
- package/dist/components/alert/snice-alert.js +41 -56
- package/dist/components/alert/snice-alert.js.map +1 -1
- package/dist/components/avatar/snice-avatar.d.ts +2 -6
- package/dist/components/avatar/snice-avatar.js +64 -71
- package/dist/components/avatar/snice-avatar.js.map +1 -1
- package/dist/components/badge/snice-badge.d.ts +2 -3
- package/dist/components/badge/snice-badge.js +22 -23
- package/dist/components/badge/snice-badge.js.map +1 -1
- package/dist/components/breadcrumbs/snice-breadcrumbs.d.ts +5 -12
- package/dist/components/breadcrumbs/snice-breadcrumbs.js +88 -89
- package/dist/components/breadcrumbs/snice-breadcrumbs.js.map +1 -1
- package/dist/components/button/snice-button.d.ts +3 -7
- package/dist/components/button/snice-button.js +37 -58
- package/dist/components/button/snice-button.js.map +1 -1
- package/dist/components/card/snice-card.d.ts +5 -8
- package/dist/components/card/snice-card.js +71 -56
- package/dist/components/card/snice-card.js.map +1 -1
- package/dist/components/checkbox/snice-checkbox.d.ts +4 -13
- package/dist/components/checkbox/snice-checkbox.js +66 -137
- package/dist/components/checkbox/snice-checkbox.js.map +1 -1
- package/dist/components/chip/snice-chip.d.ts +5 -11
- package/dist/components/chip/snice-chip.js +44 -47
- package/dist/components/chip/snice-chip.js.map +1 -1
- package/dist/components/date-picker/snice-date-picker.d.ts +11 -11
- package/dist/components/date-picker/snice-date-picker.js +134 -133
- package/dist/components/date-picker/snice-date-picker.js.map +1 -1
- package/dist/components/divider/snice-divider.d.ts +2 -4
- package/dist/components/divider/snice-divider.js +14 -22
- package/dist/components/divider/snice-divider.js.map +1 -1
- package/dist/components/drawer/snice-drawer.d.ts +4 -4
- package/dist/components/drawer/snice-drawer.js +25 -19
- package/dist/components/drawer/snice-drawer.js.map +1 -1
- package/dist/components/input/snice-input.d.ts +8 -6
- package/dist/components/input/snice-input.js +122 -105
- package/dist/components/input/snice-input.js.map +1 -1
- package/dist/components/layout/snice-layout-blog.d.ts +4 -4
- package/dist/components/layout/snice-layout-blog.js +21 -19
- package/dist/components/layout/snice-layout-blog.js.map +1 -1
- package/dist/components/layout/snice-layout-card.d.ts +2 -2
- package/dist/components/layout/snice-layout-card.js +16 -9
- package/dist/components/layout/snice-layout-card.js.map +1 -1
- package/dist/components/layout/snice-layout-centered.d.ts +2 -2
- package/dist/components/layout/snice-layout-centered.js +14 -7
- package/dist/components/layout/snice-layout-centered.js.map +1 -1
- package/dist/components/layout/snice-layout-dashboard.d.ts +5 -5
- package/dist/components/layout/snice-layout-dashboard.js +38 -30
- package/dist/components/layout/snice-layout-dashboard.js.map +1 -1
- package/dist/components/layout/snice-layout-fullscreen.d.ts +2 -2
- package/dist/components/layout/snice-layout-fullscreen.js +17 -10
- package/dist/components/layout/snice-layout-fullscreen.js.map +1 -1
- package/dist/components/layout/snice-layout-landing.d.ts +4 -4
- package/dist/components/layout/snice-layout-landing.js +21 -19
- package/dist/components/layout/snice-layout-landing.js.map +1 -1
- package/dist/components/layout/snice-layout-minimal.d.ts +2 -2
- package/dist/components/layout/snice-layout-minimal.js +17 -6
- package/dist/components/layout/snice-layout-minimal.js.map +1 -1
- package/dist/components/layout/snice-layout-sidebar.d.ts +5 -4
- package/dist/components/layout/snice-layout-sidebar.js +42 -20
- package/dist/components/layout/snice-layout-sidebar.js.map +1 -1
- package/dist/components/layout/snice-layout-split.d.ts +2 -2
- package/dist/components/layout/snice-layout-split.js +14 -7
- package/dist/components/layout/snice-layout-split.js.map +1 -1
- package/dist/components/layout/snice-layout.d.ts +4 -4
- package/dist/components/layout/snice-layout.js +16 -10
- package/dist/components/layout/snice-layout.js.map +1 -1
- package/dist/components/login/snice-login.d.ts +6 -11
- package/dist/components/login/snice-login.js +97 -71
- package/dist/components/login/snice-login.js.map +1 -1
- package/dist/components/modal/snice-modal.d.ts +5 -9
- package/dist/components/modal/snice-modal.js +47 -78
- package/dist/components/modal/snice-modal.js.map +1 -1
- package/dist/components/nav/snice-nav.d.ts +13 -7
- package/dist/components/nav/snice-nav.js +191 -100
- package/dist/components/nav/snice-nav.js.map +1 -1
- package/dist/components/nav/snice-nav.types.d.ts +3 -3
- package/dist/components/pagination/snice-pagination.d.ts +6 -7
- package/dist/components/pagination/snice-pagination.js +94 -81
- package/dist/components/pagination/snice-pagination.js.map +1 -1
- package/dist/components/progress/snice-progress.d.ts +2 -7
- package/dist/components/progress/snice-progress.js +41 -98
- package/dist/components/progress/snice-progress.js.map +1 -1
- package/dist/components/radio/snice-radio.d.ts +4 -4
- package/dist/components/radio/snice-radio.js +52 -44
- package/dist/components/radio/snice-radio.js.map +1 -1
- package/dist/components/select/snice-option.d.ts +2 -1
- package/dist/components/select/snice-option.js +12 -5
- package/dist/components/select/snice-option.js.map +1 -1
- package/dist/components/select/snice-select.d.ts +9 -21
- package/dist/components/select/snice-select.js +98 -170
- package/dist/components/select/snice-select.js.map +1 -1
- package/dist/components/skeleton/snice-skeleton.d.ts +2 -6
- package/dist/components/skeleton/snice-skeleton.js +18 -49
- package/dist/components/skeleton/snice-skeleton.js.map +1 -1
- package/dist/components/snice-cell-BLFVdxPp.js +4 -0
- package/dist/components/snice-cell-BLFVdxPp.js.map +1 -0
- package/dist/components/switch/snice-switch.d.ts +2 -2
- package/dist/components/switch/snice-switch.js +38 -26
- package/dist/components/switch/snice-switch.js.map +1 -1
- package/dist/components/table/snice-cell-actions.d.ts +24 -0
- package/dist/components/table/snice-cell-actions.js +149 -0
- package/dist/components/table/snice-cell-actions.js.map +1 -0
- package/dist/components/table/snice-cell-boolean.d.ts +2 -2
- package/dist/components/table/snice-cell-boolean.js +13 -7
- package/dist/components/table/snice-cell-boolean.js.map +1 -1
- package/dist/components/table/snice-cell-color.d.ts +18 -0
- package/dist/components/table/snice-cell-color.js +149 -0
- package/dist/components/table/snice-cell-color.js.map +1 -0
- package/dist/components/table/snice-cell-currency.d.ts +24 -0
- package/dist/components/table/snice-cell-currency.js +235 -0
- package/dist/components/table/snice-cell-currency.js.map +1 -0
- package/dist/components/table/snice-cell-date.d.ts +2 -2
- package/dist/components/table/snice-cell-date.js +14 -8
- package/dist/components/table/snice-cell-date.js.map +1 -1
- package/dist/components/table/snice-cell-duration.d.ts +2 -2
- package/dist/components/table/snice-cell-duration.js +12 -6
- package/dist/components/table/snice-cell-duration.js.map +1 -1
- package/dist/components/table/snice-cell-email.d.ts +15 -0
- package/dist/components/table/snice-cell-email.js +125 -0
- package/dist/components/table/snice-cell-email.js.map +1 -0
- package/dist/components/table/snice-cell-filesize.d.ts +2 -2
- package/dist/components/table/snice-cell-filesize.js +12 -6
- package/dist/components/table/snice-cell-filesize.js.map +1 -1
- package/dist/components/table/snice-cell-image.d.ts +20 -0
- package/dist/components/table/snice-cell-image.js +162 -0
- package/dist/components/table/snice-cell-image.js.map +1 -0
- package/dist/components/table/snice-cell-json.d.ts +20 -0
- package/dist/components/table/snice-cell-json.js +186 -0
- package/dist/components/table/snice-cell-json.js.map +1 -0
- package/dist/components/table/snice-cell-link.d.ts +17 -0
- package/dist/components/table/snice-cell-link.js +142 -0
- package/dist/components/table/snice-cell-link.js.map +1 -0
- package/dist/components/table/snice-cell-location.d.ts +19 -0
- package/dist/components/table/snice-cell-location.js +185 -0
- package/dist/components/table/snice-cell-location.js.map +1 -0
- package/dist/components/table/snice-cell-number.d.ts +2 -2
- package/dist/components/table/snice-cell-number.js +12 -6
- package/dist/components/table/snice-cell-number.js.map +1 -1
- package/dist/components/table/snice-cell-percentage.d.ts +22 -0
- package/dist/components/table/snice-cell-percentage.js +208 -0
- package/dist/components/table/snice-cell-percentage.js.map +1 -0
- package/dist/components/table/snice-cell-phone.d.ts +18 -0
- package/dist/components/table/snice-cell-phone.js +153 -0
- package/dist/components/table/snice-cell-phone.js.map +1 -0
- package/dist/components/table/snice-cell-progress.d.ts +2 -2
- package/dist/components/table/snice-cell-progress.js +12 -6
- package/dist/components/table/snice-cell-progress.js.map +1 -1
- package/dist/components/table/snice-cell-rating.d.ts +2 -2
- package/dist/components/table/snice-cell-rating.js +12 -6
- package/dist/components/table/snice-cell-rating.js.map +1 -1
- package/dist/components/table/snice-cell-sparkline.d.ts +2 -2
- package/dist/components/table/snice-cell-sparkline.js +13 -7
- package/dist/components/table/snice-cell-sparkline.js.map +1 -1
- package/dist/components/table/snice-cell-status.d.ts +17 -0
- package/dist/components/table/snice-cell-status.js +144 -0
- package/dist/components/table/snice-cell-status.js.map +1 -0
- package/dist/components/table/snice-cell-tag.d.ts +16 -0
- package/dist/components/table/snice-cell-tag.js +131 -0
- package/dist/components/table/snice-cell-tag.js.map +1 -0
- package/dist/components/table/snice-cell-text.d.ts +2 -2
- package/dist/components/table/snice-cell-text.js +14 -8
- package/dist/components/table/snice-cell-text.js.map +1 -1
- package/dist/components/table/snice-cell.d.ts +2 -2
- package/dist/components/table/snice-cell.js +12 -6
- package/dist/components/table/snice-cell.js.map +1 -1
- package/dist/components/table/snice-column.d.ts +1 -1
- package/dist/components/table/snice-column.js +6 -3
- package/dist/components/table/snice-column.js.map +1 -1
- package/dist/components/table/snice-header.d.ts +5 -5
- package/dist/components/table/snice-header.js +60 -50
- package/dist/components/table/snice-header.js.map +1 -1
- package/dist/components/table/snice-progress.d.ts +2 -2
- package/dist/components/table/snice-progress.js +18 -11
- package/dist/components/table/snice-progress.js.map +1 -1
- package/dist/components/table/snice-rating.d.ts +2 -2
- package/dist/components/table/snice-rating.js +15 -8
- package/dist/components/table/snice-rating.js.map +1 -1
- package/dist/components/table/snice-row.d.ts +17 -6
- package/dist/components/table/snice-row.js +95 -44
- package/dist/components/table/snice-row.js.map +1 -1
- package/dist/components/table/snice-table.d.ts +18 -10
- package/dist/components/table/snice-table.js +355 -173
- package/dist/components/table/snice-table.js.map +1 -1
- package/dist/components/table/snice-table.types.d.ts +101 -2
- package/dist/components/tabs/snice-tab-panel.d.ts +2 -2
- package/dist/components/tabs/snice-tab-panel.js +12 -6
- package/dist/components/tabs/snice-tab-panel.js.map +1 -1
- package/dist/components/tabs/snice-tab.d.ts +6 -5
- package/dist/components/tabs/snice-tab.js +36 -19
- package/dist/components/tabs/snice-tab.js.map +1 -1
- package/dist/components/tabs/snice-tabs.d.ts +5 -5
- package/dist/components/tabs/snice-tabs.js +38 -28
- package/dist/components/tabs/snice-tabs.js.map +1 -1
- package/dist/components/toast/snice-toast-container.d.ts +7 -7
- package/dist/components/toast/snice-toast-container.js +19 -12
- package/dist/components/toast/snice-toast-container.js.map +1 -1
- package/dist/components/toast/snice-toast.d.ts +3 -15
- package/dist/components/toast/snice-toast.js +49 -108
- package/dist/components/toast/snice-toast.js.map +1 -1
- package/dist/components/tooltip/snice-tooltip.d.ts +2 -2
- package/dist/components/tooltip/snice-tooltip.js +14 -7
- package/dist/components/tooltip/snice-tooltip.js.map +1 -1
- package/dist/context.d.ts +44 -0
- package/dist/element-ready.d.ts +40 -0
- package/dist/{types/element.d.ts → element.d.ts} +2 -8
- package/dist/{types/events.d.ts → events.d.ts} +0 -4
- package/dist/index.cjs +2589 -605
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +21 -0
- package/dist/index.esm.js +2568 -604
- package/dist/index.esm.js.map +1 -1
- package/dist/index.iife.js +2589 -605
- package/dist/index.iife.js.map +1 -1
- package/dist/method-decorators.d.ts +121 -0
- package/dist/on.d.ts +59 -0
- package/dist/parts.d.ts +159 -0
- package/dist/render-debug.d.ts +27 -0
- package/dist/render-tracker.d.ts +14 -0
- package/dist/render.d.ts +96 -0
- package/dist/symbols.cjs +163 -0
- package/dist/symbols.cjs.map +1 -1
- package/dist/{types/symbols.d.ts → symbols.d.ts} +22 -0
- package/dist/symbols.esm.js +27 -3
- package/dist/symbols.esm.js.map +1 -1
- package/dist/template.d.ts +100 -0
- package/dist/transitions.cjs +219 -0
- package/dist/transitions.esm.js +2 -2
- package/dist/types/context.d.ts +48 -0
- package/dist/types/element-options.d.ts +26 -0
- package/dist/types/index.d.ts +25 -9
- package/dist/types/nav-context.d.ts +19 -0
- package/dist/types/{types/on-options.d.ts → on-options.d.ts} +2 -0
- package/dist/types/{types/placard.d.ts → placard.d.ts} +0 -1
- package/docs/ai/README.md +17 -0
- package/docs/ai/api.md +175 -0
- package/docs/ai/architecture.md +160 -0
- package/docs/ai/components/accordion.md +174 -0
- package/docs/ai/components/alert.md +77 -0
- package/docs/ai/components/avatar.md +61 -0
- package/docs/ai/components/badge.md +69 -0
- package/docs/ai/components/breadcrumbs.md +74 -0
- package/docs/ai/components/button.md +75 -0
- package/docs/ai/components/card.md +61 -0
- package/docs/ai/components/checkbox.md +74 -0
- package/docs/ai/components/chip.md +73 -0
- package/docs/ai/components/date-picker.md +75 -0
- package/docs/ai/components/divider.md +66 -0
- package/docs/ai/components/drawer.md +80 -0
- package/docs/ai/components/input.md +111 -0
- package/docs/ai/components/login.md +109 -0
- package/docs/ai/components/modal.md +67 -0
- package/docs/ai/components/nav.md +76 -0
- package/docs/ai/components/pagination.md +55 -0
- package/docs/ai/components/progress.md +72 -0
- package/docs/ai/components/radio.md +79 -0
- package/docs/ai/components/select.md +92 -0
- package/docs/ai/components/skeleton.md +57 -0
- package/docs/ai/components/switch.md +53 -0
- package/docs/ai/components/table.md +227 -0
- package/docs/ai/components/tabs.md +83 -0
- package/docs/ai/components/toast.md +140 -0
- package/docs/ai/components/tooltip.md +146 -0
- package/docs/ai/patterns.md +244 -0
- package/docs/components/accordion.md +558 -0
- package/docs/components/drawer.md +602 -0
- package/docs/components/modal.md +558 -0
- package/docs/components/nav.md +239 -0
- package/docs/components/pagination.md +289 -0
- package/docs/components/select.md +599 -0
- package/docs/components/switch.md +354 -0
- package/docs/components/tabs.md +546 -0
- package/docs/components/toast.md +506 -0
- package/docs/components/tooltip.md +523 -0
- package/docs/controllers.md +744 -0
- package/docs/elements.md +855 -0
- package/docs/events.md +807 -0
- package/docs/migration-v2-to-v3.md +569 -0
- package/docs/observe.md +588 -0
- package/docs/placards.md +401 -0
- package/docs/request-response.md +852 -0
- package/docs/routing.md +1186 -0
- package/package.json +10 -11
- package/dist/components/snice-cell-C9N6yGxQ.js +0 -4
- package/dist/components/snice-cell-C9N6yGxQ.js.map +0 -1
- package/dist/types/types/index.d.ts +0 -23
- /package/dist/{types/controller.d.ts → controller.d.ts} +0 -0
- /package/dist/{types/global.d.ts → global.d.ts} +0 -0
- /package/dist/{types/observe.d.ts → observe.d.ts} +0 -0
- /package/dist/{types/request-response.d.ts → request-response.d.ts} +0 -0
- /package/dist/{types/router.d.ts → router.d.ts} +0 -0
- /package/dist/{types/testing.d.ts → testing.d.ts} +0 -0
- /package/dist/{types/transitions.d.ts → transitions.d.ts} +0 -0
- /package/dist/types/{types/adopted-options.d.ts → adopted-options.d.ts} +0 -0
- /package/dist/types/{types/app-context.d.ts → app-context.d.ts} +0 -0
- /package/dist/types/{types/dispatch-options.d.ts → dispatch-options.d.ts} +0 -0
- /package/dist/types/{types/guard.d.ts → guard.d.ts} +0 -0
- /package/dist/types/{types/i-controller.d.ts → i-controller.d.ts} +0 -0
- /package/dist/types/{types/moved-options.d.ts → moved-options.d.ts} +0 -0
- /package/dist/types/{types/observe-options.d.ts → observe-options.d.ts} +0 -0
- /package/dist/types/{types/page-options.d.ts → page-options.d.ts} +0 -0
- /package/dist/types/{types/part-options.d.ts → part-options.d.ts} +0 -0
- /package/dist/types/{types/property-converter.d.ts → property-converter.d.ts} +0 -0
- /package/dist/types/{types/property-options.d.ts → property-options.d.ts} +0 -0
- /package/dist/types/{types/query-options.d.ts → query-options.d.ts} +0 -0
- /package/dist/types/{types/request-options.d.ts → request-options.d.ts} +0 -0
- /package/dist/types/{types/respond-options.d.ts → respond-options.d.ts} +0 -0
- /package/dist/types/{types/route-params.d.ts → route-params.d.ts} +0 -0
- /package/dist/types/{types/router-instance.d.ts → router-instance.d.ts} +0 -0
- /package/dist/types/{types/router-options.d.ts → router-options.d.ts} +0 -0
- /package/dist/types/{types/simple-array.d.ts → simple-array.d.ts} +0 -0
- /package/dist/types/{types/snice-element.d.ts → snice-element.d.ts} +0 -0
- /package/dist/types/{types/snice-global.d.ts → snice-global.d.ts} +0 -0
- /package/dist/types/{types/transition.d.ts → transition.d.ts} +0 -0
- /package/dist/{types/utils.d.ts → utils.d.ts} +0 -0
package/docs/elements.md
ADDED
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
# Elements API Documentation
|
|
2
|
+
|
|
3
|
+
Elements are the core building blocks of Snice components. They define custom HTML elements with encapsulated styling and behavior using web components.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Basic Usage](#basic-usage)
|
|
7
|
+
- [Lifecycle Methods](#lifecycle-methods)
|
|
8
|
+
- [Shadow DOM](#shadow-dom)
|
|
9
|
+
- [Properties](#properties)
|
|
10
|
+
- [Queries](#queries)
|
|
11
|
+
- [Styling](#styling)
|
|
12
|
+
- [Template Events](#template-events)
|
|
13
|
+
- [Advanced Examples](#advanced-examples)
|
|
14
|
+
|
|
15
|
+
## Basic Usage
|
|
16
|
+
|
|
17
|
+
### Creating an Element
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { element, render, html } from 'snice';
|
|
21
|
+
|
|
22
|
+
@element('my-button')
|
|
23
|
+
class MyButton extends HTMLElement {
|
|
24
|
+
@render()
|
|
25
|
+
renderContent() {
|
|
26
|
+
return html`<button>Click me</button>`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Element Decorator Options
|
|
32
|
+
|
|
33
|
+
The `@element` decorator accepts a single parameter:
|
|
34
|
+
- `tagName: string` - The custom element tag name (must contain a hyphen)
|
|
35
|
+
|
|
36
|
+
## Lifecycle Methods
|
|
37
|
+
|
|
38
|
+
### @render() Decorator
|
|
39
|
+
|
|
40
|
+
Returns a template using the `html` tagged template. Automatically re-renders when properties change due to differential rendering.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { element, render, html, property } from 'snice';
|
|
44
|
+
|
|
45
|
+
@element('user-card')
|
|
46
|
+
class UserCard extends HTMLElement {
|
|
47
|
+
@property()
|
|
48
|
+
name = 'Anonymous';
|
|
49
|
+
|
|
50
|
+
@render()
|
|
51
|
+
renderContent() {
|
|
52
|
+
return html`
|
|
53
|
+
<div class="card">
|
|
54
|
+
<h3>${this.name}</h3>
|
|
55
|
+
<p>User details...</p>
|
|
56
|
+
</div>
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Auto-Rendering:**
|
|
63
|
+
- Template automatically re-renders when `@property()` decorated properties change
|
|
64
|
+
- Only changed parts of the DOM update (differential rendering)
|
|
65
|
+
- No manual re-render calls needed
|
|
66
|
+
|
|
67
|
+
**Render Options:**
|
|
68
|
+
|
|
69
|
+
The `@render()` decorator accepts an optional configuration object:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
@render({
|
|
73
|
+
debounce?: number, // Delay re-render (ms)
|
|
74
|
+
throttle?: number, // Limit re-render frequency (ms)
|
|
75
|
+
once?: boolean, // Render once, disable auto-rendering
|
|
76
|
+
sync?: boolean, // Synchronous rendering (skip batching)
|
|
77
|
+
differential?: boolean // Disable differential rendering (default: true)
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Differential Rendering:**
|
|
82
|
+
|
|
83
|
+
By default, Snice uses differential rendering which only updates changed parts of the DOM. To disable this and re-render from scratch each time:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
@element('simple-list')
|
|
87
|
+
class SimpleList extends HTMLElement {
|
|
88
|
+
@property({ type: Array })
|
|
89
|
+
items = [];
|
|
90
|
+
|
|
91
|
+
@render({ differential: false })
|
|
92
|
+
renderContent() {
|
|
93
|
+
// Must return a string when differential: false
|
|
94
|
+
return `
|
|
95
|
+
<ul>
|
|
96
|
+
${this.items.map(item => `<li>${item}</li>`).join('')}
|
|
97
|
+
</ul>
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**When to use `differential: false`:**
|
|
104
|
+
- Component has complex dynamic structure that changes between renders
|
|
105
|
+
- Template structure changes based on data (e.g., empty state vs. populated)
|
|
106
|
+
- Avoiding differential rendering issues with dynamic attributes
|
|
107
|
+
- Simple components where full re-render is acceptable
|
|
108
|
+
|
|
109
|
+
**Note:** When `differential: false`, the render method must return a string (not `html\`...\``) and still honors `<if>` and `<switch>/<case>` meta elements.
|
|
110
|
+
|
|
111
|
+
### @styles() Decorator
|
|
112
|
+
|
|
113
|
+
Returns CSS using the `css` tagged template, scoped to the element's shadow DOM.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { element, render, styles, html, css } from 'snice';
|
|
117
|
+
|
|
118
|
+
@element('styled-card')
|
|
119
|
+
class StyledCard extends HTMLElement {
|
|
120
|
+
@render()
|
|
121
|
+
renderContent() {
|
|
122
|
+
return html`<div class="card">Content</div>`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@styles()
|
|
126
|
+
cardStyles() {
|
|
127
|
+
return css`
|
|
128
|
+
.card {
|
|
129
|
+
padding: 20px;
|
|
130
|
+
border: 1px solid #ddd;
|
|
131
|
+
border-radius: 8px;
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Multiple Style Methods:**
|
|
139
|
+
```typescript
|
|
140
|
+
@styles()
|
|
141
|
+
baseStyles() {
|
|
142
|
+
return css`
|
|
143
|
+
:host { display: block; }
|
|
144
|
+
// ...
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@styles()
|
|
149
|
+
themeStyles() {
|
|
150
|
+
return css`
|
|
151
|
+
.card { background: var(--bg-color); }
|
|
152
|
+
// ...
|
|
153
|
+
`;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Lifecycle Decorators
|
|
158
|
+
|
|
159
|
+
**@ready()** - Called after shadow DOM is ready and initial render completes:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { element, ready, render, html } from 'snice';
|
|
163
|
+
|
|
164
|
+
@element('data-loader')
|
|
165
|
+
class DataLoader extends HTMLElement {
|
|
166
|
+
@ready()
|
|
167
|
+
async loadData() {
|
|
168
|
+
// Called after element is fully initialized
|
|
169
|
+
const data = await fetch('/api/data').then(r => r.json());
|
|
170
|
+
this.data = data;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@render()
|
|
174
|
+
renderContent() {
|
|
175
|
+
return html`<div>Loading...</div>`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**@dispose()** - Called when element is removed from DOM:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
@element('polling-element')
|
|
184
|
+
class PollingElement extends HTMLElement {
|
|
185
|
+
private intervalId?: number;
|
|
186
|
+
|
|
187
|
+
@ready()
|
|
188
|
+
startPolling() {
|
|
189
|
+
this.intervalId = setInterval(() => {
|
|
190
|
+
this.updateData();
|
|
191
|
+
}, 5000);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@dispose()
|
|
195
|
+
stopPolling() {
|
|
196
|
+
if (this.intervalId) {
|
|
197
|
+
clearInterval(this.intervalId);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@render()
|
|
202
|
+
renderContent() {
|
|
203
|
+
return html`<div>Polling...</div>`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### ready Promise
|
|
209
|
+
|
|
210
|
+
Every element has a `ready` promise that resolves when fully initialized:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
const el = document.createElement('my-element') as MyElement;
|
|
214
|
+
document.body.appendChild(el);
|
|
215
|
+
await (el as any).ready; // Wait for element to be ready
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Shadow DOM
|
|
219
|
+
|
|
220
|
+
All elements automatically use Shadow DOM for style encapsulation.
|
|
221
|
+
|
|
222
|
+
### Accessing Shadow Root
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
@element('shadow-demo')
|
|
226
|
+
class ShadowDemo extends HTMLElement {
|
|
227
|
+
@render()
|
|
228
|
+
renderContent() {
|
|
229
|
+
return html`<div id="content">Hello</div>`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
updateContent(text: string) {
|
|
233
|
+
const content = this.shadowRoot?.getElementById('content');
|
|
234
|
+
if (content) {
|
|
235
|
+
content.textContent = text;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Properties
|
|
242
|
+
|
|
243
|
+
### Basic Properties
|
|
244
|
+
|
|
245
|
+
Properties automatically sync with DOM attributes and trigger re-renders:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { element, property, render, html } from 'snice';
|
|
249
|
+
|
|
250
|
+
@element('user-profile')
|
|
251
|
+
class UserProfile extends HTMLElement {
|
|
252
|
+
@property()
|
|
253
|
+
name = 'Anonymous';
|
|
254
|
+
|
|
255
|
+
@property({ type: Number })
|
|
256
|
+
age = 0;
|
|
257
|
+
|
|
258
|
+
@property({ type: Boolean })
|
|
259
|
+
verified = false;
|
|
260
|
+
|
|
261
|
+
@render()
|
|
262
|
+
renderContent() {
|
|
263
|
+
return html`
|
|
264
|
+
<div>
|
|
265
|
+
<h3>${this.name}</h3>
|
|
266
|
+
<p>Age: ${this.age}</p>
|
|
267
|
+
${this.verified ? html`<span>✓ Verified</span>` : ''}
|
|
268
|
+
</div>
|
|
269
|
+
`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Usage:
|
|
275
|
+
```html
|
|
276
|
+
<user-profile name="John Doe" age="30" verified></user-profile>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Property Options
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
interface PropertyOptions {
|
|
283
|
+
type?: String | Number | Boolean | Array | Object | Date | BigInt | SimpleArray;
|
|
284
|
+
attribute?: string; // Custom attribute name
|
|
285
|
+
converter?: PropertyConverter; // Custom converter
|
|
286
|
+
hasChanged?: (value: any, oldValue: any) => boolean;
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Property Behavior
|
|
291
|
+
|
|
292
|
+
All properties automatically:
|
|
293
|
+
- Read from DOM attributes when present
|
|
294
|
+
- Reflect changes to corresponding attributes
|
|
295
|
+
- Convert between string attributes and typed properties
|
|
296
|
+
- Trigger re-renders when changed
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
@element('reflected-props')
|
|
300
|
+
class ReflectedProps extends HTMLElement {
|
|
301
|
+
@property()
|
|
302
|
+
theme = 'light';
|
|
303
|
+
|
|
304
|
+
@property({ attribute: 'user-id' })
|
|
305
|
+
userId = '';
|
|
306
|
+
|
|
307
|
+
@render()
|
|
308
|
+
renderContent() {
|
|
309
|
+
return html`<div class="${this.theme}">User: ${this.userId}</div>`;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Boolean Properties:**
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
@property({ type: Boolean })
|
|
318
|
+
enabled = false;
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
- `<element>` or `<element enabled="">` → `true`
|
|
322
|
+
- `<element enabled="true">` → `true`
|
|
323
|
+
- `<element enabled="false">` → `false`
|
|
324
|
+
- No attribute → `false`
|
|
325
|
+
|
|
326
|
+
### Custom Converters
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
const dateConverter: PropertyConverter = {
|
|
330
|
+
fromAttribute(value: string | null): Date | null {
|
|
331
|
+
return value ? new Date(value) : null;
|
|
332
|
+
},
|
|
333
|
+
toAttribute(value: Date | null): string | null {
|
|
334
|
+
return value ? value.toISOString() : null;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
@element('date-display')
|
|
339
|
+
class DateDisplay extends HTMLElement {
|
|
340
|
+
@property({ converter: dateConverter })
|
|
341
|
+
date: Date | null = null;
|
|
342
|
+
|
|
343
|
+
@render()
|
|
344
|
+
renderContent() {
|
|
345
|
+
return html`<time>${this.date?.toLocaleDateString() || 'No date'}</time>`;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### SimpleArray Type
|
|
351
|
+
|
|
352
|
+
The `SimpleArray` type enables safe reflection of arrays containing basic types:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { element, property, SimpleArray, render, html } from 'snice';
|
|
356
|
+
|
|
357
|
+
@element('tag-list')
|
|
358
|
+
class TagList extends HTMLElement {
|
|
359
|
+
@property({ type: SimpleArray })
|
|
360
|
+
tags = ['javascript', 'typescript', 'web'];
|
|
361
|
+
|
|
362
|
+
@render()
|
|
363
|
+
renderContent() {
|
|
364
|
+
return html`
|
|
365
|
+
<ul>
|
|
366
|
+
${this.tags.map(tag => html`<li>${tag}</li>`)}
|
|
367
|
+
</ul>
|
|
368
|
+
`;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Usage:
|
|
374
|
+
```html
|
|
375
|
+
<tag-list tags="react,vue,angular"></tag-list>
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
- Uses full-width comma (,) as separator
|
|
379
|
+
- Supports string, number, and boolean types
|
|
380
|
+
- Type-safe serialization
|
|
381
|
+
|
|
382
|
+
## Queries
|
|
383
|
+
|
|
384
|
+
### Single Element Query
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
import { element, query, render, html } from 'snice';
|
|
388
|
+
|
|
389
|
+
@element('form-component')
|
|
390
|
+
class FormComponent extends HTMLElement {
|
|
391
|
+
@query('input[type="text"]')
|
|
392
|
+
textInput?: HTMLInputElement;
|
|
393
|
+
|
|
394
|
+
@query('button[type="submit"]')
|
|
395
|
+
submitButton?: HTMLButtonElement;
|
|
396
|
+
|
|
397
|
+
@render()
|
|
398
|
+
renderContent() {
|
|
399
|
+
return html`
|
|
400
|
+
<form>
|
|
401
|
+
<input type="text" placeholder="Enter text">
|
|
402
|
+
<button type="submit">Submit</button>
|
|
403
|
+
</form>
|
|
404
|
+
`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
getValue(): string {
|
|
408
|
+
return this.textInput?.value || '';
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Multiple Elements Query
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
@element('todo-list')
|
|
417
|
+
class TodoList extends HTMLElement {
|
|
418
|
+
@queryAll('.todo-item')
|
|
419
|
+
todoItems?: NodeListOf<HTMLElement>;
|
|
420
|
+
|
|
421
|
+
@queryAll('input[type="checkbox"]')
|
|
422
|
+
checkboxes?: NodeListOf<HTMLInputElement>;
|
|
423
|
+
|
|
424
|
+
@render()
|
|
425
|
+
renderContent() {
|
|
426
|
+
return html`
|
|
427
|
+
<ul>
|
|
428
|
+
<li class="todo-item"><input type="checkbox"> Task 1</li>
|
|
429
|
+
<li class="todo-item"><input type="checkbox"> Task 2</li>
|
|
430
|
+
</ul>
|
|
431
|
+
`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
getCompletedCount(): number {
|
|
435
|
+
if (!this.checkboxes) return 0;
|
|
436
|
+
return Array.from(this.checkboxes).filter(cb => cb.checked).length;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Query Options
|
|
442
|
+
|
|
443
|
+
Control where queries search using `light` and `shadow` options:
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
@element('query-options')
|
|
447
|
+
class QueryOptions extends HTMLElement {
|
|
448
|
+
// Query only in shadow DOM (default)
|
|
449
|
+
@query('.shadow-only')
|
|
450
|
+
shadowElement?: HTMLElement;
|
|
451
|
+
|
|
452
|
+
// Query only in light DOM (slotted content)
|
|
453
|
+
@query('.light-only', { light: true, shadow: false })
|
|
454
|
+
lightElement?: HTMLElement;
|
|
455
|
+
|
|
456
|
+
// Query in both light and shadow DOM
|
|
457
|
+
@query('.anywhere', { light: true, shadow: true })
|
|
458
|
+
anyElement?: HTMLElement;
|
|
459
|
+
|
|
460
|
+
@render()
|
|
461
|
+
renderContent() {
|
|
462
|
+
return html`
|
|
463
|
+
<div class="shadow-only">Shadow Content</div>
|
|
464
|
+
<slot></slot>
|
|
465
|
+
`;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## Styling
|
|
471
|
+
|
|
472
|
+
### Scoped Styles
|
|
473
|
+
|
|
474
|
+
Styles are automatically scoped to the component's shadow DOM:
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
@element('scoped-styles')
|
|
478
|
+
class ScopedStyles extends HTMLElement {
|
|
479
|
+
@render()
|
|
480
|
+
renderContent() {
|
|
481
|
+
return html`
|
|
482
|
+
<div class="container">
|
|
483
|
+
<h1>Title</h1>
|
|
484
|
+
<p class="content">Content</p>
|
|
485
|
+
</div>
|
|
486
|
+
`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
@styles()
|
|
490
|
+
componentStyles() {
|
|
491
|
+
return css`
|
|
492
|
+
:host {
|
|
493
|
+
display: block;
|
|
494
|
+
padding: 20px;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.container {
|
|
498
|
+
border: 1px solid #ccc;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
h1 {
|
|
502
|
+
color: blue; /* Only affects h1 inside this component */
|
|
503
|
+
}
|
|
504
|
+
`;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Dynamic Styles
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
@element('theme-component')
|
|
513
|
+
class ThemeComponent extends HTMLElement {
|
|
514
|
+
@property()
|
|
515
|
+
primaryColor = '#007bff';
|
|
516
|
+
|
|
517
|
+
@property({ type: Number })
|
|
518
|
+
fontSize = 16;
|
|
519
|
+
|
|
520
|
+
@render()
|
|
521
|
+
renderContent() {
|
|
522
|
+
return html`<div class="themed">Themed content</div>`;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
@styles()
|
|
526
|
+
themeStyles() {
|
|
527
|
+
return css`
|
|
528
|
+
.themed {
|
|
529
|
+
color: ${this.primaryColor};
|
|
530
|
+
font-size: ${this.fontSize}px;
|
|
531
|
+
}
|
|
532
|
+
`;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Host Styling
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
@element('host-styled')
|
|
541
|
+
class HostStyled extends HTMLElement {
|
|
542
|
+
@styles()
|
|
543
|
+
hostStyles() {
|
|
544
|
+
return css`
|
|
545
|
+
:host {
|
|
546
|
+
display: block;
|
|
547
|
+
width: 100%;
|
|
548
|
+
max-width: 600px;
|
|
549
|
+
margin: 0 auto;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
:host([disabled]) {
|
|
553
|
+
opacity: 0.5;
|
|
554
|
+
pointer-events: none;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
:host(:hover) {
|
|
558
|
+
background: #f0f0f0;
|
|
559
|
+
}
|
|
560
|
+
`;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
@render()
|
|
564
|
+
renderContent() {
|
|
565
|
+
return html`<div>Content</div>`;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
## Template Events
|
|
571
|
+
|
|
572
|
+
Handle events directly in templates using `@event=${handler}` syntax:
|
|
573
|
+
|
|
574
|
+
### Basic Event Handling
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
@element('click-counter')
|
|
578
|
+
class ClickCounter extends HTMLElement {
|
|
579
|
+
@property({ type: Number })
|
|
580
|
+
count = 0;
|
|
581
|
+
|
|
582
|
+
@render()
|
|
583
|
+
renderContent() {
|
|
584
|
+
return html`
|
|
585
|
+
<button @click=${this.increment}>Click me</button>
|
|
586
|
+
<span>Count: ${this.count}</span>
|
|
587
|
+
`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
increment() {
|
|
591
|
+
this.count++;
|
|
592
|
+
// Auto re-renders due to property change
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### Keyboard Shortcuts
|
|
598
|
+
|
|
599
|
+
Use dot notation for keyboard shortcuts:
|
|
600
|
+
|
|
601
|
+
```typescript
|
|
602
|
+
@element('keyboard-handler')
|
|
603
|
+
class KeyboardHandler extends HTMLElement {
|
|
604
|
+
@render()
|
|
605
|
+
renderContent() {
|
|
606
|
+
return html`
|
|
607
|
+
<input @keydown.enter=${this.handleEnter} placeholder="Press Enter">
|
|
608
|
+
<input @keydown.ctrl+s=${this.handleSave} placeholder="Press Ctrl+S">
|
|
609
|
+
<input @keydown.escape=${this.handleCancel} placeholder="Press Escape">
|
|
610
|
+
`;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
handleEnter(e: KeyboardEvent) {
|
|
614
|
+
console.log('Enter pressed');
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
handleSave(e: KeyboardEvent) {
|
|
618
|
+
e.preventDefault();
|
|
619
|
+
console.log('Saving...');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
handleCancel() {
|
|
623
|
+
console.log('Cancelled');
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
Keyboard syntax:
|
|
629
|
+
- `@keydown.enter` - Plain Enter (no modifiers)
|
|
630
|
+
- `@keydown.ctrl+s` - Ctrl+S combination
|
|
631
|
+
- `@keydown.~enter` - Enter with any modifiers
|
|
632
|
+
- `@keydown.escape`, `@keydown.down`, etc.
|
|
633
|
+
|
|
634
|
+
### Event Object Access
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
@element('form-handler')
|
|
638
|
+
class FormHandler extends HTMLElement {
|
|
639
|
+
@render()
|
|
640
|
+
renderContent() {
|
|
641
|
+
return html`
|
|
642
|
+
<form @submit=${this.handleSubmit}>
|
|
643
|
+
<input type="text" name="username" @input=${this.handleInput}>
|
|
644
|
+
<button type="submit">Submit</button>
|
|
645
|
+
</form>
|
|
646
|
+
`;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
handleSubmit(event: Event) {
|
|
650
|
+
event.preventDefault();
|
|
651
|
+
const form = event.target as HTMLFormElement;
|
|
652
|
+
const formData = new FormData(form);
|
|
653
|
+
console.log('Form data:', Object.fromEntries(formData));
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
handleInput(event: Event) {
|
|
657
|
+
const input = event.target as HTMLInputElement;
|
|
658
|
+
console.log('Input changed:', input.value);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
## Advanced Examples
|
|
664
|
+
|
|
665
|
+
### Complex Form Component
|
|
666
|
+
|
|
667
|
+
```typescript
|
|
668
|
+
import { element, property, query, watch, render, styles, html, css } from 'snice';
|
|
669
|
+
|
|
670
|
+
@element('registration-form')
|
|
671
|
+
class RegistrationForm extends HTMLElement {
|
|
672
|
+
@property({ type: Boolean })
|
|
673
|
+
loading = false;
|
|
674
|
+
|
|
675
|
+
@query('form')
|
|
676
|
+
form?: HTMLFormElement;
|
|
677
|
+
|
|
678
|
+
@render()
|
|
679
|
+
renderContent() {
|
|
680
|
+
return html`
|
|
681
|
+
<form @submit=${this.handleSubmit}>
|
|
682
|
+
<h2>Register</h2>
|
|
683
|
+
|
|
684
|
+
<div class="field">
|
|
685
|
+
<label>Username</label>
|
|
686
|
+
<input type="text" name="username" required>
|
|
687
|
+
</div>
|
|
688
|
+
|
|
689
|
+
<div class="field">
|
|
690
|
+
<label>Email</label>
|
|
691
|
+
<input type="email" name="email" required>
|
|
692
|
+
</div>
|
|
693
|
+
|
|
694
|
+
<button type="submit" ?disabled=${this.loading}>
|
|
695
|
+
${this.loading ? 'Registering...' : 'Register'}
|
|
696
|
+
</button>
|
|
697
|
+
</form>
|
|
698
|
+
`;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
@styles()
|
|
702
|
+
formStyles() {
|
|
703
|
+
return css`
|
|
704
|
+
:host {
|
|
705
|
+
display: block;
|
|
706
|
+
max-width: 400px;
|
|
707
|
+
margin: 0 auto;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
form {
|
|
711
|
+
padding: 20px;
|
|
712
|
+
background: white;
|
|
713
|
+
border-radius: 8px;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
.field {
|
|
717
|
+
margin-bottom: 20px;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
label {
|
|
721
|
+
display: block;
|
|
722
|
+
margin-bottom: 5px;
|
|
723
|
+
font-weight: bold;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
input {
|
|
727
|
+
width: 100%;
|
|
728
|
+
padding: 8px 12px;
|
|
729
|
+
border: 1px solid #ddd;
|
|
730
|
+
border-radius: 4px;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
button {
|
|
734
|
+
width: 100%;
|
|
735
|
+
padding: 10px;
|
|
736
|
+
background: #007bff;
|
|
737
|
+
color: white;
|
|
738
|
+
border: none;
|
|
739
|
+
border-radius: 4px;
|
|
740
|
+
cursor: pointer;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
button:disabled {
|
|
744
|
+
opacity: 0.6;
|
|
745
|
+
cursor: not-allowed;
|
|
746
|
+
}
|
|
747
|
+
`;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async handleSubmit(event: Event) {
|
|
751
|
+
event.preventDefault();
|
|
752
|
+
|
|
753
|
+
this.loading = true;
|
|
754
|
+
|
|
755
|
+
try {
|
|
756
|
+
const formData = new FormData(this.form!);
|
|
757
|
+
|
|
758
|
+
// Simulate API call
|
|
759
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
760
|
+
|
|
761
|
+
this.dispatchEvent(new CustomEvent('registration-success', {
|
|
762
|
+
detail: Object.fromEntries(formData),
|
|
763
|
+
bubbles: true
|
|
764
|
+
}));
|
|
765
|
+
|
|
766
|
+
this.form?.reset();
|
|
767
|
+
} catch (error) {
|
|
768
|
+
console.error('Registration failed:', error);
|
|
769
|
+
} finally {
|
|
770
|
+
this.loading = false;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### Watch Decorator
|
|
777
|
+
|
|
778
|
+
Use `@watch` to react to property changes:
|
|
779
|
+
|
|
780
|
+
```typescript
|
|
781
|
+
@element('reactive-component')
|
|
782
|
+
class ReactiveComponent extends HTMLElement {
|
|
783
|
+
@property()
|
|
784
|
+
userName = '';
|
|
785
|
+
|
|
786
|
+
@property({ type: Number })
|
|
787
|
+
score = 0;
|
|
788
|
+
|
|
789
|
+
@watch('userName')
|
|
790
|
+
onUserNameChange(oldVal: string, newVal: string) {
|
|
791
|
+
console.log(`Name changed from ${oldVal} to ${newVal}`);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
@watch('score')
|
|
795
|
+
onScoreChange(oldVal: number, newVal: number) {
|
|
796
|
+
if (newVal > 100) {
|
|
797
|
+
console.log('High score achieved!');
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
@render()
|
|
802
|
+
renderContent() {
|
|
803
|
+
return html`
|
|
804
|
+
<div>
|
|
805
|
+
<h1>${this.userName}</h1>
|
|
806
|
+
<p>Score: ${this.score}</p>
|
|
807
|
+
</div>
|
|
808
|
+
`;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
### Conditional Rendering
|
|
814
|
+
|
|
815
|
+
```typescript
|
|
816
|
+
@element('conditional-content')
|
|
817
|
+
class ConditionalContent extends HTMLElement {
|
|
818
|
+
@property({ type: Boolean })
|
|
819
|
+
isLoggedIn = false;
|
|
820
|
+
|
|
821
|
+
@render()
|
|
822
|
+
renderContent() {
|
|
823
|
+
return html`
|
|
824
|
+
<if ${this.isLoggedIn}>
|
|
825
|
+
<div>Welcome back!</div>
|
|
826
|
+
<button @click=${this.logout}>Logout</button>
|
|
827
|
+
</if>
|
|
828
|
+
|
|
829
|
+
<if ${!this.isLoggedIn}>
|
|
830
|
+
<a href="/login">Please login</a>
|
|
831
|
+
</if>
|
|
832
|
+
`;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
logout() {
|
|
836
|
+
this.isLoggedIn = false;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
## Best Practices
|
|
842
|
+
|
|
843
|
+
1. **Use @render() for templates**: Always return `html\`...\`` tagged templates
|
|
844
|
+
2. **Use @styles() for CSS**: Always return `css\`...\`` tagged templates
|
|
845
|
+
3. **Leverage auto-rendering**: Properties automatically trigger re-renders
|
|
846
|
+
4. **Use template events**: Handle events with `@event=${handler}` in templates
|
|
847
|
+
5. **Clean up resources**: Use @dispose() for cleanup tasks
|
|
848
|
+
6. **Type your queries**: Use proper TypeScript types for queried elements
|
|
849
|
+
7. **Handle errors**: Wrap async operations in try-catch blocks
|
|
850
|
+
|
|
851
|
+
## Removed in v3.0.0
|
|
852
|
+
|
|
853
|
+
- **@part decorator**: Removed in favor of differential rendering. Use `@render()` with templates instead.
|
|
854
|
+
- **html() method**: Replaced with `@render()` decorator
|
|
855
|
+
- **css() method**: Replaced with `@styles()` decorator
|