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/events.md
ADDED
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
# Events API Documentation
|
|
2
|
+
|
|
3
|
+
Event handling in Snice provides two powerful approaches: **template event syntax** and the **`@on` decorator** (fully restored from v2.5.4!). The `@on` decorator now works in **both elements AND controllers** with full event delegation, keyboard modifiers, debounce/throttle, and more. Additionally, the `@dispatch` decorator enables automatic custom event dispatching.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Template Event Syntax](#template-event-syntax)
|
|
7
|
+
- [@on Decorator (v2.5.4 RESTORED!)](#on-decorator-v254-restored)
|
|
8
|
+
- [@dispatch Decorator](#dispatch-decorator)
|
|
9
|
+
- [Custom Events](#custom-events)
|
|
10
|
+
- [Event Delegation](#event-delegation)
|
|
11
|
+
- [Keyboard Shortcuts](#keyboard-shortcuts)
|
|
12
|
+
- [Best Practices](#best-practices)
|
|
13
|
+
|
|
14
|
+
## Template Event Syntax (Preferred for Elements)
|
|
15
|
+
|
|
16
|
+
The recommended way to handle events in elements is using template event syntax with `@event=${handler}`:
|
|
17
|
+
|
|
18
|
+
### Basic Usage
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { element, property, render, html } from 'snice';
|
|
22
|
+
|
|
23
|
+
@element('click-counter')
|
|
24
|
+
class ClickCounter extends HTMLElement {
|
|
25
|
+
@property({ type: Number })
|
|
26
|
+
count = 0;
|
|
27
|
+
|
|
28
|
+
@render()
|
|
29
|
+
renderContent() {
|
|
30
|
+
return html`
|
|
31
|
+
<div class="counter">
|
|
32
|
+
<button @click=${this.increment}>Increment</button>
|
|
33
|
+
<button @click=${this.decrement}>Decrement</button>
|
|
34
|
+
<button @click=${this.reset}>Reset</button>
|
|
35
|
+
<span class="count">${this.count}</span>
|
|
36
|
+
</div>
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
increment() {
|
|
41
|
+
this.count++;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
decrement() {
|
|
45
|
+
this.count--;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
reset() {
|
|
49
|
+
this.count = 0;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Event Object Access
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
@element('form-handler')
|
|
58
|
+
class FormHandler extends HTMLElement {
|
|
59
|
+
@render()
|
|
60
|
+
renderContent() {
|
|
61
|
+
return html`
|
|
62
|
+
<form @submit=${this.handleSubmit}>
|
|
63
|
+
<input
|
|
64
|
+
type="text"
|
|
65
|
+
name="username"
|
|
66
|
+
@input=${this.handleInput}
|
|
67
|
+
@focus=${this.handleFocus}
|
|
68
|
+
@blur=${this.handleBlur}
|
|
69
|
+
>
|
|
70
|
+
<button type="submit">Submit</button>
|
|
71
|
+
</form>
|
|
72
|
+
`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
handleSubmit(event: Event) {
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
const form = event.target as HTMLFormElement;
|
|
78
|
+
const formData = new FormData(form);
|
|
79
|
+
console.log('Form submitted:', Object.fromEntries(formData));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
handleInput(event: Event) {
|
|
83
|
+
const input = event.target as HTMLInputElement;
|
|
84
|
+
console.log('Input value:', input.value);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
handleFocus(event: Event) {
|
|
88
|
+
console.log('Input focused');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
handleBlur(event: Event) {
|
|
92
|
+
console.log('Input blurred');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Multiple Event Types
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
@element('file-upload')
|
|
101
|
+
class FileUpload extends HTMLElement {
|
|
102
|
+
@property({ type: Boolean })
|
|
103
|
+
dragOver = false;
|
|
104
|
+
|
|
105
|
+
@render()
|
|
106
|
+
renderContent() {
|
|
107
|
+
return html`
|
|
108
|
+
<div
|
|
109
|
+
class="dropzone ${this.dragOver ? 'drag-over' : ''}"
|
|
110
|
+
@dragenter=${this.handleDragEnter}
|
|
111
|
+
@dragover=${this.handleDragOver}
|
|
112
|
+
@dragleave=${this.handleDragLeave}
|
|
113
|
+
@drop=${this.handleDrop}
|
|
114
|
+
>
|
|
115
|
+
Drop files here
|
|
116
|
+
</div>
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
handleDragEnter(e: DragEvent) {
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
this.dragOver = true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
handleDragOver(e: DragEvent) {
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
handleDragLeave(e: DragEvent) {
|
|
130
|
+
this.dragOver = false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
handleDrop(e: DragEvent) {
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
this.dragOver = false;
|
|
136
|
+
const files = Array.from(e.dataTransfer?.files || []);
|
|
137
|
+
console.log('Files dropped:', files);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Keyboard Shortcuts in Templates
|
|
143
|
+
|
|
144
|
+
Template event syntax supports keyboard shortcuts using dot notation:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
@element('keyboard-input')
|
|
148
|
+
class KeyboardInput extends HTMLElement {
|
|
149
|
+
@render()
|
|
150
|
+
renderContent() {
|
|
151
|
+
return html`
|
|
152
|
+
<div>
|
|
153
|
+
<input
|
|
154
|
+
@keydown.enter=${this.handleEnter}
|
|
155
|
+
placeholder="Press Enter"
|
|
156
|
+
>
|
|
157
|
+
|
|
158
|
+
<input
|
|
159
|
+
@keydown.ctrl+s=${this.handleSave}
|
|
160
|
+
placeholder="Press Ctrl+S"
|
|
161
|
+
>
|
|
162
|
+
|
|
163
|
+
<input
|
|
164
|
+
@keydown.escape=${this.handleCancel}
|
|
165
|
+
placeholder="Press Escape"
|
|
166
|
+
>
|
|
167
|
+
|
|
168
|
+
<input
|
|
169
|
+
@keydown.~enter=${this.handleAnyEnter}
|
|
170
|
+
placeholder="Press Enter with any modifiers"
|
|
171
|
+
>
|
|
172
|
+
</div>
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
handleEnter(e: KeyboardEvent) {
|
|
177
|
+
console.log('Enter pressed (no modifiers)');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
handleSave(e: KeyboardEvent) {
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
console.log('Ctrl+S pressed');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
handleCancel(e: KeyboardEvent) {
|
|
186
|
+
console.log('Escape pressed');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
handleAnyEnter(e: KeyboardEvent) {
|
|
190
|
+
console.log('Enter pressed (with any modifiers)');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Keyboard Shortcut Syntax:**
|
|
196
|
+
- `@keydown.enter` - Plain Enter (no modifiers)
|
|
197
|
+
- `@keydown.ctrl+s` - Ctrl+S combination
|
|
198
|
+
- `@keydown.ctrl+shift+s` - Multiple modifiers
|
|
199
|
+
- `@keydown.~enter` - Enter with any modifiers
|
|
200
|
+
- `@keydown.escape`, `@keydown.down`, etc. - Named keys
|
|
201
|
+
|
|
202
|
+
### Arrow Functions in Templates
|
|
203
|
+
|
|
204
|
+
Use arrow functions for inline event handling or passing parameters:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
@element('task-list')
|
|
208
|
+
class TaskList extends HTMLElement {
|
|
209
|
+
@property()
|
|
210
|
+
tasks = [
|
|
211
|
+
{ id: 1, name: 'Task 1', completed: false },
|
|
212
|
+
{ id: 2, name: 'Task 2', completed: false }
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
@render()
|
|
216
|
+
renderContent() {
|
|
217
|
+
return html`
|
|
218
|
+
<ul>
|
|
219
|
+
${this.tasks.map(task => html`
|
|
220
|
+
<li>
|
|
221
|
+
<input
|
|
222
|
+
type="checkbox"
|
|
223
|
+
?checked=${task.completed}
|
|
224
|
+
@change=${(e: Event) => this.toggleTask(task.id, e)}
|
|
225
|
+
>
|
|
226
|
+
<span>${task.name}</span>
|
|
227
|
+
<button @click=${() => this.deleteTask(task.id)}>Delete</button>
|
|
228
|
+
</li>
|
|
229
|
+
`)}
|
|
230
|
+
</ul>
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
toggleTask(id: number, e: Event) {
|
|
235
|
+
const checkbox = e.target as HTMLInputElement;
|
|
236
|
+
const task = this.tasks.find(t => t.id === id);
|
|
237
|
+
if (task) {
|
|
238
|
+
task.completed = checkbox.checked;
|
|
239
|
+
// Trigger re-render
|
|
240
|
+
this.tasks = [...this.tasks];
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
deleteTask(id: number) {
|
|
245
|
+
this.tasks = this.tasks.filter(t => t.id !== id);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## @on Decorator (v2.5.4 RESTORED!)
|
|
251
|
+
|
|
252
|
+
The `@on` decorator is **fully restored from v2.5.4** and now works in **both elements AND controllers**! It provides powerful event delegation, keyboard modifiers, debounce/throttle, and automatic event handling features.
|
|
253
|
+
|
|
254
|
+
**Use `@on` when you need:**
|
|
255
|
+
- Event delegation with CSS selectors
|
|
256
|
+
- Keyboard modifier matching (`Enter`, `ctrl+s`, etc.)
|
|
257
|
+
- Debounce or throttle
|
|
258
|
+
- Multiple events on one handler
|
|
259
|
+
- Automatic preventDefault or stopPropagation
|
|
260
|
+
|
|
261
|
+
### Basic Controller Usage
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { controller, on, IController } from 'snice';
|
|
265
|
+
|
|
266
|
+
@controller('button-controller')
|
|
267
|
+
class ButtonController implements IController {
|
|
268
|
+
element: HTMLElement | null = null;
|
|
269
|
+
|
|
270
|
+
async attach(element: HTMLElement) {
|
|
271
|
+
console.log('Controller attached');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async detach(element: HTMLElement) {
|
|
275
|
+
console.log('Controller detached');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@on('click')
|
|
279
|
+
handleClick(event: MouseEvent) {
|
|
280
|
+
console.log('Button clicked');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
@on('mouseenter')
|
|
284
|
+
handleMouseEnter(event: MouseEvent) {
|
|
285
|
+
console.log('Mouse entered');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@on('mouseleave')
|
|
289
|
+
handleMouseLeave(event: MouseEvent) {
|
|
290
|
+
console.log('Mouse left');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Event Delegation with Selector
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
@controller('list-controller')
|
|
299
|
+
class ListController implements IController {
|
|
300
|
+
element: HTMLElement | null = null;
|
|
301
|
+
|
|
302
|
+
async attach(element: HTMLElement) {}
|
|
303
|
+
async detach(element: HTMLElement) {}
|
|
304
|
+
|
|
305
|
+
// Handle clicks on list items
|
|
306
|
+
@on('click', '.list-item')
|
|
307
|
+
handleItemClick(event: MouseEvent) {
|
|
308
|
+
const item = event.target as HTMLElement;
|
|
309
|
+
console.log('Item clicked:', item.textContent);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Handle clicks on delete buttons
|
|
313
|
+
@on('click', '.delete-button')
|
|
314
|
+
handleDeleteClick(event: MouseEvent) {
|
|
315
|
+
event.stopPropagation();
|
|
316
|
+
const button = event.target as HTMLElement;
|
|
317
|
+
const item = button.closest('.list-item');
|
|
318
|
+
item?.remove();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Handle input on text fields
|
|
322
|
+
@on('input', 'input[type="text"]')
|
|
323
|
+
handleTextInput(event: Event) {
|
|
324
|
+
const input = event.target as HTMLInputElement;
|
|
325
|
+
console.log('Text changed:', input.value);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Keyboard Events with @on
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
@controller('editor-controller')
|
|
334
|
+
class EditorController implements IController {
|
|
335
|
+
element: HTMLElement | null = null;
|
|
336
|
+
|
|
337
|
+
async attach(element: HTMLElement) {}
|
|
338
|
+
async detach(element: HTMLElement) {}
|
|
339
|
+
|
|
340
|
+
@on('keydown:Enter', 'textarea')
|
|
341
|
+
handleEnter(event: KeyboardEvent) {
|
|
342
|
+
console.log('Enter pressed in textarea');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
@on('keydown:Ctrl+S')
|
|
346
|
+
handleSave(event: KeyboardEvent) {
|
|
347
|
+
event.preventDefault();
|
|
348
|
+
console.log('Save shortcut triggered');
|
|
349
|
+
this.save();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
@on('keydown:Escape')
|
|
353
|
+
handleEscape(event: KeyboardEvent) {
|
|
354
|
+
console.log('Escape pressed');
|
|
355
|
+
this.cancel();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private save() {
|
|
359
|
+
console.log('Saving...');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private cancel() {
|
|
363
|
+
console.log('Cancelling...');
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### @on Options
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
interface OnOptions {
|
|
372
|
+
// Standard event listener options
|
|
373
|
+
capture?: boolean; // Use capture phase instead of bubble phase
|
|
374
|
+
once?: boolean; // Remove listener after first trigger
|
|
375
|
+
passive?: boolean; // Passive listener (can't preventDefault)
|
|
376
|
+
|
|
377
|
+
// Automatic event handling
|
|
378
|
+
preventDefault?: boolean; // Automatically call preventDefault on the event
|
|
379
|
+
stopPropagation?: boolean; // Automatically call stopPropagation on the event
|
|
380
|
+
|
|
381
|
+
// Timing controls (v3.0.0 enhancements!)
|
|
382
|
+
debounce?: number; // Debounce the handler by specified milliseconds
|
|
383
|
+
throttle?: number; // Throttle the handler by specified milliseconds
|
|
384
|
+
|
|
385
|
+
// Event delegation
|
|
386
|
+
target?: string; // CSS selector to target specific elements within shadow root
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
#### Throttling
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
@controller('scroll-controller')
|
|
394
|
+
class ScrollController implements IController {
|
|
395
|
+
element: HTMLElement | null = null;
|
|
396
|
+
|
|
397
|
+
async attach(element: HTMLElement) {}
|
|
398
|
+
async detach(element: HTMLElement) {}
|
|
399
|
+
|
|
400
|
+
// Throttle scroll events to max once per 100ms
|
|
401
|
+
@on('scroll', null, { throttle: 100 })
|
|
402
|
+
handleScroll(event: Event) {
|
|
403
|
+
const element = event.target as HTMLElement;
|
|
404
|
+
console.log('Scroll position:', element.scrollTop);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
#### Debouncing
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
@controller('search-controller')
|
|
413
|
+
class SearchController implements IController {
|
|
414
|
+
element: HTMLElement | null = null;
|
|
415
|
+
|
|
416
|
+
async attach(element: HTMLElement) {}
|
|
417
|
+
async detach(element: HTMLElement) {}
|
|
418
|
+
|
|
419
|
+
// Debounce input events by 300ms
|
|
420
|
+
@on('input', 'input[type="search"]', { debounce: 300 })
|
|
421
|
+
handleSearch(event: Event) {
|
|
422
|
+
const input = event.target as HTMLInputElement;
|
|
423
|
+
console.log('Searching for:', input.value);
|
|
424
|
+
this.performSearch(input.value);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
private async performSearch(query: string) {
|
|
428
|
+
// Search implementation
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Using @on in Elements (Alternative)
|
|
434
|
+
|
|
435
|
+
While template syntax is preferred, `@on` can also be used in elements:
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
import { element, on, render, html } from 'snice';
|
|
439
|
+
|
|
440
|
+
@element('legacy-button')
|
|
441
|
+
class LegacyButton extends HTMLElement {
|
|
442
|
+
@render()
|
|
443
|
+
renderContent() {
|
|
444
|
+
return html`<button class="btn">Click me</button>`;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
@on('click', '.btn')
|
|
448
|
+
handleClick(event: MouseEvent) {
|
|
449
|
+
console.log('Button clicked via @on decorator');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
@on('input', 'input', { debounce: 300 })
|
|
453
|
+
handleInput(event: Event) {
|
|
454
|
+
console.log('Input debounced');
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**Note:** For new element code, prefer template event syntax for better readability and type safety.
|
|
460
|
+
|
|
461
|
+
## @dispatch Decorator
|
|
462
|
+
|
|
463
|
+
Auto-dispatch custom events after method execution:
|
|
464
|
+
|
|
465
|
+
### Basic Usage
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
import { element, dispatch, render, html } from 'snice';
|
|
469
|
+
|
|
470
|
+
@element('value-input')
|
|
471
|
+
class ValueInput extends HTMLElement {
|
|
472
|
+
private value = '';
|
|
473
|
+
|
|
474
|
+
@render()
|
|
475
|
+
renderContent() {
|
|
476
|
+
return html`
|
|
477
|
+
<input
|
|
478
|
+
type="text"
|
|
479
|
+
.value=${this.value}
|
|
480
|
+
@input=${this.handleInput}
|
|
481
|
+
>
|
|
482
|
+
`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
handleInput(e: Event) {
|
|
486
|
+
const input = e.target as HTMLInputElement;
|
|
487
|
+
this.setValue(input.value);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
@dispatch('value-changed')
|
|
491
|
+
setValue(newValue: string) {
|
|
492
|
+
this.value = newValue;
|
|
493
|
+
return { value: newValue }; // Event detail
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Usage:
|
|
499
|
+
```typescript
|
|
500
|
+
const input = document.querySelector('value-input');
|
|
501
|
+
input.addEventListener('value-changed', (e: CustomEvent) => {
|
|
502
|
+
console.log('New value:', e.detail.value);
|
|
503
|
+
});
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Event Options
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
@element('status-indicator')
|
|
510
|
+
class StatusIndicator extends HTMLElement {
|
|
511
|
+
@render()
|
|
512
|
+
renderContent() {
|
|
513
|
+
return html`<div>Status</div>`;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
@dispatch('status-changed', { bubbles: true, composed: true })
|
|
517
|
+
updateStatus(status: string) {
|
|
518
|
+
return {
|
|
519
|
+
status,
|
|
520
|
+
timestamp: Date.now()
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Multiple Dispatches
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
@element('data-manager')
|
|
530
|
+
class DataManager extends HTMLElement {
|
|
531
|
+
private data: any[] = [];
|
|
532
|
+
|
|
533
|
+
@render()
|
|
534
|
+
renderContent() {
|
|
535
|
+
return html`<div>Data Manager</div>`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
@dispatch('item-added')
|
|
539
|
+
addItem(item: any) {
|
|
540
|
+
this.data.push(item);
|
|
541
|
+
return { item, total: this.data.length };
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
@dispatch('item-removed')
|
|
545
|
+
removeItem(id: string) {
|
|
546
|
+
const item = this.data.find(i => i.id === id);
|
|
547
|
+
this.data = this.data.filter(i => i.id !== id);
|
|
548
|
+
return { id, item, total: this.data.length };
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
@dispatch('data-cleared')
|
|
552
|
+
clearData() {
|
|
553
|
+
const count = this.data.length;
|
|
554
|
+
this.data = [];
|
|
555
|
+
return { clearedCount: count };
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
## Custom Events
|
|
561
|
+
|
|
562
|
+
### Dispatching Manually
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
@element('manual-dispatcher')
|
|
566
|
+
class ManualDispatcher extends HTMLElement {
|
|
567
|
+
@render()
|
|
568
|
+
renderContent() {
|
|
569
|
+
return html`
|
|
570
|
+
<button @click=${this.notify}>Notify</button>
|
|
571
|
+
`;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
notify() {
|
|
575
|
+
// Dispatch custom event manually
|
|
576
|
+
this.dispatchEvent(new CustomEvent('notification', {
|
|
577
|
+
detail: { message: 'Hello!', level: 'info' },
|
|
578
|
+
bubbles: true,
|
|
579
|
+
composed: true
|
|
580
|
+
}));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Listening to Custom Events
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
@element('event-listener')
|
|
589
|
+
class EventListener extends HTMLElement {
|
|
590
|
+
@render()
|
|
591
|
+
renderContent() {
|
|
592
|
+
return html`
|
|
593
|
+
<manual-dispatcher @notification=${this.handleNotification}></manual-dispatcher>
|
|
594
|
+
`;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
handleNotification(e: CustomEvent) {
|
|
598
|
+
console.log('Received notification:', e.detail);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
## Event Delegation
|
|
604
|
+
|
|
605
|
+
### Controller Event Delegation
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
@controller('table-controller')
|
|
609
|
+
class TableController implements IController {
|
|
610
|
+
element: HTMLElement | null = null;
|
|
611
|
+
|
|
612
|
+
async attach(element: HTMLElement) {}
|
|
613
|
+
async detach(element: HTMLElement) {}
|
|
614
|
+
|
|
615
|
+
// Single event listener handles all rows
|
|
616
|
+
@on('click', 'tr')
|
|
617
|
+
handleRowClick(event: MouseEvent) {
|
|
618
|
+
const row = event.currentTarget as HTMLTableRowElement;
|
|
619
|
+
console.log('Row clicked:', row.dataset.id);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Handle button clicks in cells
|
|
623
|
+
@on('click', 'button.edit')
|
|
624
|
+
handleEdit(event: MouseEvent) {
|
|
625
|
+
const button = event.target as HTMLButtonElement;
|
|
626
|
+
const row = button.closest('tr');
|
|
627
|
+
console.log('Edit row:', row?.dataset.id);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
@on('click', 'button.delete')
|
|
631
|
+
handleDelete(event: MouseEvent) {
|
|
632
|
+
event.stopPropagation(); // Don't trigger row click
|
|
633
|
+
const button = event.target as HTMLButtonElement;
|
|
634
|
+
const row = button.closest('tr');
|
|
635
|
+
row?.remove();
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### Template Event Delegation
|
|
641
|
+
|
|
642
|
+
For dynamic content, use controllers with `@on` for event delegation, or handle events on a parent element:
|
|
643
|
+
|
|
644
|
+
```typescript
|
|
645
|
+
@element('dynamic-list')
|
|
646
|
+
class DynamicList extends HTMLElement {
|
|
647
|
+
@property()
|
|
648
|
+
items = ['Item 1', 'Item 2', 'Item 3'];
|
|
649
|
+
|
|
650
|
+
@render()
|
|
651
|
+
renderContent() {
|
|
652
|
+
return html`
|
|
653
|
+
<ul @click=${this.handleListClick}>
|
|
654
|
+
${this.items.map((item, index) => html`
|
|
655
|
+
<li data-index="${index}">
|
|
656
|
+
${item}
|
|
657
|
+
<button class="delete">Delete</button>
|
|
658
|
+
</li>
|
|
659
|
+
`)}
|
|
660
|
+
</ul>
|
|
661
|
+
`;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
handleListClick(e: MouseEvent) {
|
|
665
|
+
const target = e.target as HTMLElement;
|
|
666
|
+
|
|
667
|
+
// Handle delete button
|
|
668
|
+
if (target.classList.contains('delete')) {
|
|
669
|
+
const li = target.closest('li');
|
|
670
|
+
const index = parseInt(li?.dataset.index || '-1');
|
|
671
|
+
if (index >= 0) {
|
|
672
|
+
this.items = this.items.filter((_, i) => i !== index);
|
|
673
|
+
}
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Handle li click
|
|
678
|
+
if (target.tagName === 'LI') {
|
|
679
|
+
console.log('Item clicked:', target.textContent);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
## Keyboard Shortcuts
|
|
686
|
+
|
|
687
|
+
### Template Syntax (Preferred)
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
@element('shortcut-handler')
|
|
691
|
+
class ShortcutHandler extends HTMLElement {
|
|
692
|
+
@render()
|
|
693
|
+
renderContent() {
|
|
694
|
+
return html`
|
|
695
|
+
<div>
|
|
696
|
+
<input @keydown.enter=${this.submit} placeholder="Press Enter">
|
|
697
|
+
<input @keydown.ctrl+s=${this.save} placeholder="Ctrl+S to save">
|
|
698
|
+
<input @keydown.ctrl+shift+s=${this.saveAs} placeholder="Ctrl+Shift+S for Save As">
|
|
699
|
+
<input @keydown.escape=${this.cancel} placeholder="Escape to cancel">
|
|
700
|
+
<input @keydown.~enter=${this.submitAny} placeholder="Enter with any mods">
|
|
701
|
+
</div>
|
|
702
|
+
`;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
submit(e: KeyboardEvent) {
|
|
706
|
+
console.log('Submit');
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
save(e: KeyboardEvent) {
|
|
710
|
+
e.preventDefault();
|
|
711
|
+
console.log('Save');
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
saveAs(e: KeyboardEvent) {
|
|
715
|
+
e.preventDefault();
|
|
716
|
+
console.log('Save As');
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
cancel(e: KeyboardEvent) {
|
|
720
|
+
console.log('Cancel');
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
submitAny(e: KeyboardEvent) {
|
|
724
|
+
console.log('Submit with any modifiers');
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
### @on Decorator Syntax
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
@controller('keyboard-controller')
|
|
733
|
+
class KeyboardController implements IController {
|
|
734
|
+
element: HTMLElement | null = null;
|
|
735
|
+
|
|
736
|
+
async attach(element: HTMLElement) {}
|
|
737
|
+
async detach(element: HTMLElement) {}
|
|
738
|
+
|
|
739
|
+
@on('keydown:Enter')
|
|
740
|
+
handleEnter(e: KeyboardEvent) {
|
|
741
|
+
console.log('Enter pressed');
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
@on('keydown:Ctrl+S')
|
|
745
|
+
handleSave(e: KeyboardEvent) {
|
|
746
|
+
e.preventDefault();
|
|
747
|
+
console.log('Save');
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
@on('keydown:Escape')
|
|
751
|
+
handleEscape(e: KeyboardEvent) {
|
|
752
|
+
console.log('Escape');
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
## Best Practices
|
|
758
|
+
|
|
759
|
+
### For Elements
|
|
760
|
+
|
|
761
|
+
1. **Prefer template syntax**: Use `@event=${handler}` in templates
|
|
762
|
+
2. **Use arrow functions for parameters**: `@click=${() => this.delete(id)}`
|
|
763
|
+
3. **Handle events at appropriate level**: Use event delegation for dynamic content
|
|
764
|
+
4. **Prevent default when needed**: Call `e.preventDefault()` in handlers
|
|
765
|
+
5. **Use keyboard shortcuts**: Leverage `@keydown.ctrl+s` syntax
|
|
766
|
+
|
|
767
|
+
### For Controllers
|
|
768
|
+
|
|
769
|
+
1. **Use @on decorator**: Controllers should use `@on` for event handling
|
|
770
|
+
2. **Leverage event delegation**: Use selectors to handle dynamic content
|
|
771
|
+
3. **Throttle/debounce high-frequency events**: Use `{ throttle }` or `{ debounce }` options
|
|
772
|
+
4. **Clean up automatically**: Event listeners are cleaned up on detach
|
|
773
|
+
5. **Handle keyboard shortcuts**: Use `@on('keydown:Ctrl+S')` syntax
|
|
774
|
+
|
|
775
|
+
### General
|
|
776
|
+
|
|
777
|
+
1. **Type your events**: Use TypeScript event types for type safety
|
|
778
|
+
2. **Stop propagation carefully**: Only use `stopPropagation()` when necessary
|
|
779
|
+
3. **Use custom events**: Dispatch custom events for component communication
|
|
780
|
+
4. **Compose events**: Use `{ composed: true }` for cross-shadow-DOM events
|
|
781
|
+
5. **Test event handlers**: Write tests for all event handling logic
|
|
782
|
+
|
|
783
|
+
## Event Types Reference
|
|
784
|
+
|
|
785
|
+
**Mouse Events:**
|
|
786
|
+
- `click`, `dblclick`, `mousedown`, `mouseup`
|
|
787
|
+
- `mouseenter`, `mouseleave`, `mousemove`, `mouseover`, `mouseout`
|
|
788
|
+
- `contextmenu`
|
|
789
|
+
|
|
790
|
+
**Keyboard Events:**
|
|
791
|
+
- `keydown`, `keyup`, `keypress`
|
|
792
|
+
|
|
793
|
+
**Form Events:**
|
|
794
|
+
- `input`, `change`, `submit`, `reset`
|
|
795
|
+
- `focus`, `blur`, `focusin`, `focusout`
|
|
796
|
+
|
|
797
|
+
**Drag Events:**
|
|
798
|
+
- `drag`, `dragstart`, `dragend`
|
|
799
|
+
- `dragenter`, `dragover`, `dragleave`, `drop`
|
|
800
|
+
|
|
801
|
+
**Touch Events:**
|
|
802
|
+
- `touchstart`, `touchmove`, `touchend`, `touchcancel`
|
|
803
|
+
|
|
804
|
+
**Other Events:**
|
|
805
|
+
- `scroll`, `resize`, `load`, `error`
|
|
806
|
+
- `animationstart`, `animationend`, `animationiteration`
|
|
807
|
+
- `transitionstart`, `transitionend`
|