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/observe.md
ADDED
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
# Observe API Documentation
|
|
2
|
+
|
|
3
|
+
The `@observe` decorator provides lifecycle-managed observation of external changes like viewport intersection, element resize, media queries, and DOM mutations.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Overview](#overview)
|
|
7
|
+
- [Intersection Observer](#intersection-observer)
|
|
8
|
+
- [Resize Observer](#resize-observer)
|
|
9
|
+
- [Media Query Observer](#media-query-observer)
|
|
10
|
+
- [Mutation Observer](#mutation-observer)
|
|
11
|
+
- [Using with Controllers](#using-with-controllers)
|
|
12
|
+
- [Options](#options)
|
|
13
|
+
- [Best Practices](#best-practices)
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
The `@observe` decorator automatically manages browser observers with proper cleanup, preventing memory leaks and simplifying complex observation patterns.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { element, observe, render, html } from 'snice';
|
|
21
|
+
|
|
22
|
+
@element('lazy-image')
|
|
23
|
+
class LazyImage extends HTMLElement {
|
|
24
|
+
@observe('intersection', 'img')
|
|
25
|
+
loadImage(entry: IntersectionObserverEntry) {
|
|
26
|
+
if (entry.isIntersecting) {
|
|
27
|
+
const img = entry.target as HTMLImageElement;
|
|
28
|
+
img.src = img.dataset.src!;
|
|
29
|
+
return false; // Stop observing this element
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@render()
|
|
34
|
+
renderContent() {
|
|
35
|
+
return html`<img data-src="image.jpg" />`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Array Syntax
|
|
41
|
+
|
|
42
|
+
You can observe multiple types with a single handler using array syntax:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
@element('dynamic-content')
|
|
46
|
+
class DynamicContent extends HTMLElement {
|
|
47
|
+
@render()
|
|
48
|
+
renderContent() {
|
|
49
|
+
return html`<div class="content" data-state="initial">Content</div>`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Watch for both child changes and attribute changes
|
|
53
|
+
@observe(['mutation:childList', 'mutation:attributes'], '.content')
|
|
54
|
+
handleContentChange(mutations: MutationRecord[]) {
|
|
55
|
+
mutations.forEach(m => {
|
|
56
|
+
if (m.type === 'childList') {
|
|
57
|
+
console.log('Children changed');
|
|
58
|
+
} else if (m.type === 'attributes') {
|
|
59
|
+
console.log('Attributes changed');
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Multiple media queries with one handler
|
|
65
|
+
@observe(['media:(max-width: 768px)', 'media:(prefers-color-scheme: dark)'])
|
|
66
|
+
handleResponsiveTheme(matches: boolean) {
|
|
67
|
+
// Called for each media query independently
|
|
68
|
+
this.updateLayout();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
updateLayout() {
|
|
72
|
+
// Update logic
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Intersection Observer
|
|
78
|
+
|
|
79
|
+
Detect when elements enter or leave the viewport. Perfect for lazy loading, infinite scroll, and animations.
|
|
80
|
+
|
|
81
|
+
### Basic Usage
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
@element('scroll-trigger')
|
|
85
|
+
class ScrollTrigger extends HTMLElement {
|
|
86
|
+
@render()
|
|
87
|
+
renderContent() {
|
|
88
|
+
return html`
|
|
89
|
+
<div class="content">Scroll to see me</div>
|
|
90
|
+
<img class="lazy" data-src="image.jpg" />
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Observe when element becomes visible
|
|
95
|
+
@observe('intersection')
|
|
96
|
+
handleVisible(entry: IntersectionObserverEntry) {
|
|
97
|
+
if (entry.isIntersecting) {
|
|
98
|
+
this.classList.add('visible');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Observe specific element with threshold
|
|
103
|
+
@observe('intersection', '.lazy', { threshold: 0.1 })
|
|
104
|
+
loadImage(entry: IntersectionObserverEntry) {
|
|
105
|
+
if (entry.isIntersecting) {
|
|
106
|
+
const img = entry.target as HTMLImageElement;
|
|
107
|
+
img.src = img.dataset.src!;
|
|
108
|
+
return false; // Stop observing after loading
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Options
|
|
115
|
+
|
|
116
|
+
- `threshold`: Number or array (0-1) defining visibility percentage to trigger
|
|
117
|
+
- `rootMargin`: Margin around root to expand/shrink observation area
|
|
118
|
+
- `root`: Element to use as viewport (defaults to browser viewport)
|
|
119
|
+
|
|
120
|
+
### Stopping Observation
|
|
121
|
+
|
|
122
|
+
Return `false` from the handler to stop observing that specific element:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
@observe('intersection', '.item')
|
|
126
|
+
handleItemVisible(entry: IntersectionObserverEntry) {
|
|
127
|
+
if (entry.isIntersecting) {
|
|
128
|
+
this.animateItem(entry.target);
|
|
129
|
+
return false; // Don't observe this item anymore
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
animateItem(target: Element) {
|
|
134
|
+
// Animation logic
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Resize Observer
|
|
139
|
+
|
|
140
|
+
Monitor element size changes for responsive components.
|
|
141
|
+
|
|
142
|
+
### Basic Usage
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
@element('responsive-chart')
|
|
146
|
+
class ResponsiveChart extends HTMLElement {
|
|
147
|
+
@render()
|
|
148
|
+
renderContent() {
|
|
149
|
+
return html`<canvas class="chart"></canvas>`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Observe host element resize
|
|
153
|
+
@observe('resize')
|
|
154
|
+
handleResize(entry: ResizeObserverEntry) {
|
|
155
|
+
const { width, height } = entry.contentRect;
|
|
156
|
+
this.redrawChart(width, height);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Observe specific element with throttling
|
|
160
|
+
@observe('resize', '.chart', { throttle: 100 })
|
|
161
|
+
handleChartResize(entry: ResizeObserverEntry) {
|
|
162
|
+
this.updateChartDimensions(entry.contentRect);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
redrawChart(width: number, height: number) {
|
|
166
|
+
// Chart redraw logic
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
updateChartDimensions(rect: DOMRectReadOnly) {
|
|
170
|
+
// Update logic
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Options
|
|
176
|
+
|
|
177
|
+
- `box`: `'content-box'` or `'border-box'` (which box model to observe)
|
|
178
|
+
- `throttle`: Milliseconds to throttle resize callbacks
|
|
179
|
+
|
|
180
|
+
## Media Query Observer
|
|
181
|
+
|
|
182
|
+
Respond to viewport and user preference changes.
|
|
183
|
+
|
|
184
|
+
### Basic Usage
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
@element('responsive-layout')
|
|
188
|
+
class ResponsiveLayout extends HTMLElement {
|
|
189
|
+
isDesktop = false;
|
|
190
|
+
isDarkMode = false;
|
|
191
|
+
|
|
192
|
+
@render()
|
|
193
|
+
renderContent() {
|
|
194
|
+
return html`<div class="layout">Content</div>`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Desktop breakpoint
|
|
198
|
+
@observe('media:(min-width: 768px)')
|
|
199
|
+
handleDesktop(matches: boolean) {
|
|
200
|
+
this.isDesktop = matches;
|
|
201
|
+
this.updateLayout();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Dark mode preference
|
|
205
|
+
@observe('media:(prefers-color-scheme: dark)')
|
|
206
|
+
handleDarkMode(matches: boolean) {
|
|
207
|
+
this.isDarkMode = matches;
|
|
208
|
+
this.updateTheme();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Portrait orientation on mobile
|
|
212
|
+
@observe('media:(orientation: portrait) and (max-width: 768px)')
|
|
213
|
+
handleMobilePortrait(matches: boolean) {
|
|
214
|
+
if (matches) {
|
|
215
|
+
this.adjustForMobilePortrait();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
updateLayout() {
|
|
220
|
+
// Layout update logic
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
updateTheme() {
|
|
224
|
+
// Theme update logic
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
adjustForMobilePortrait() {
|
|
228
|
+
// Adjustment logic
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Important Notes
|
|
234
|
+
|
|
235
|
+
- Handler is called immediately with current state when observer is set up
|
|
236
|
+
- Media query strings use standard CSS media query syntax
|
|
237
|
+
- Media queries are cached globally for efficiency
|
|
238
|
+
|
|
239
|
+
## Mutation Observer
|
|
240
|
+
|
|
241
|
+
Watch for DOM changes like added/removed nodes or attribute modifications.
|
|
242
|
+
|
|
243
|
+
### Basic Usage
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
@element('dynamic-list')
|
|
247
|
+
class DynamicList extends HTMLElement {
|
|
248
|
+
@render()
|
|
249
|
+
renderContent() {
|
|
250
|
+
return html`
|
|
251
|
+
<ul class="list"></ul>
|
|
252
|
+
<div class="count">0 items</div>
|
|
253
|
+
`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Watch for child list changes
|
|
257
|
+
@observe('mutation:childList', '.list')
|
|
258
|
+
handleListChange(mutations: MutationRecord[]) {
|
|
259
|
+
const count = this.querySelector('.list')?.children.length || 0;
|
|
260
|
+
this.updateCount(count);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Watch for specific attribute changes
|
|
264
|
+
@observe('mutation:attributes:data-state', '.item')
|
|
265
|
+
handleStateChange(mutations: MutationRecord[]) {
|
|
266
|
+
const mutation = mutations[0];
|
|
267
|
+
const newState = (mutation.target as Element).getAttribute('data-state');
|
|
268
|
+
this.updateItemDisplay(mutation.target, newState);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
updateCount(count: number) {
|
|
272
|
+
const countDiv = this.querySelector('.count');
|
|
273
|
+
if (countDiv) countDiv.textContent = `${count} items`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
updateItemDisplay(target: Node, newState: string | null) {
|
|
277
|
+
// Update display logic
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Mutation Types
|
|
283
|
+
|
|
284
|
+
- `mutation:childList` - Observe added/removed child nodes
|
|
285
|
+
- `mutation:attributes` - Observe all attribute changes
|
|
286
|
+
- `mutation:attributes:name` - Observe specific attribute changes
|
|
287
|
+
|
|
288
|
+
### Options
|
|
289
|
+
|
|
290
|
+
- `subtree`: Also observe descendants (use with caution for performance)
|
|
291
|
+
- `throttle`: Milliseconds to throttle mutation callbacks
|
|
292
|
+
|
|
293
|
+
### Safety Features
|
|
294
|
+
|
|
295
|
+
- `subtree: true` is not enabled by default to prevent performance issues
|
|
296
|
+
- Character data mutations are not supported (too granular)
|
|
297
|
+
- Always be specific about what you're observing
|
|
298
|
+
|
|
299
|
+
## Using with Controllers
|
|
300
|
+
|
|
301
|
+
Controllers can also use `@observe` for separation of concerns. When used in controllers, observers operate on the attached element:
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
@controller('viewport-controller')
|
|
305
|
+
class ViewportController implements IController {
|
|
306
|
+
element: HTMLElement | null = null;
|
|
307
|
+
|
|
308
|
+
@observe('media:(min-width: 1024px)')
|
|
309
|
+
handleLargeScreen(matches: boolean) {
|
|
310
|
+
if (this.element) {
|
|
311
|
+
this.element.classList.toggle('large-screen', matches);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@observe('intersection', { threshold: 0.5 })
|
|
316
|
+
handleVisibility(entry: IntersectionObserverEntry) {
|
|
317
|
+
if (entry.isIntersecting) {
|
|
318
|
+
this.trackImpression();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async attach(element: HTMLElement) {
|
|
323
|
+
this.element = element;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async detach(element: HTMLElement) {
|
|
327
|
+
this.element = null;
|
|
328
|
+
// Observers are automatically cleaned up
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
trackImpression() {
|
|
332
|
+
// Analytics tracking
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Options
|
|
338
|
+
|
|
339
|
+
### Common Options
|
|
340
|
+
|
|
341
|
+
All observers support these options:
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
interface ObserveOptions {
|
|
345
|
+
throttle?: number; // Throttle callbacks by milliseconds
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Specific Options
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// Intersection Observer
|
|
353
|
+
interface IntersectionOptions extends ObserveOptions {
|
|
354
|
+
threshold?: number | number[]; // 0-1 visibility threshold
|
|
355
|
+
rootMargin?: string; // Margin around root
|
|
356
|
+
root?: Element | null; // Viewport element
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Resize Observer
|
|
360
|
+
interface ResizeOptions extends ObserveOptions {
|
|
361
|
+
box?: 'content-box' | 'border-box'; // Box model to observe
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Mutation Observer
|
|
365
|
+
interface MutationOptions extends ObserveOptions {
|
|
366
|
+
subtree?: boolean; // Observe descendants
|
|
367
|
+
maxDepth?: number; // Limit subtree depth
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Best Practices
|
|
372
|
+
|
|
373
|
+
### 1. Be Specific
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
// Good - specific selector and threshold
|
|
377
|
+
@observe('intersection', '.lazy-image', { threshold: 0.1 })
|
|
378
|
+
|
|
379
|
+
// Bad - observing everything
|
|
380
|
+
@observe('intersection', '*')
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### 2. Use Throttling for High-Frequency Events
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
// Good - throttled resize observer
|
|
387
|
+
@observe('resize', { throttle: 100 })
|
|
388
|
+
handleResize(entry: ResizeObserverEntry) {
|
|
389
|
+
this.expensiveOperation();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Bad - unthrottled resize can fire many times per second
|
|
393
|
+
@observe('resize')
|
|
394
|
+
handleResize(entry: ResizeObserverEntry) {
|
|
395
|
+
this.expensiveOperation();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
expensiveOperation() {
|
|
399
|
+
// Expensive logic
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### 3. Stop Observing When Done
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
@observe('intersection', '.load-more')
|
|
407
|
+
handleLoadMore(entry: IntersectionObserverEntry) {
|
|
408
|
+
if (entry.isIntersecting) {
|
|
409
|
+
this.loadMoreContent();
|
|
410
|
+
return false; // Stop observing after trigger
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
loadMoreContent() {
|
|
415
|
+
// Load more logic
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### 4. Avoid Deep Subtree Observation
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
// Bad - observing entire subtree
|
|
423
|
+
@observe('mutation:childList', { subtree: true })
|
|
424
|
+
|
|
425
|
+
// Good - observe specific container
|
|
426
|
+
@observe('mutation:childList', '.list-container')
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### 5. Use Media Queries for Responsive Design
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// Good - declarative responsive behavior
|
|
433
|
+
@observe('media:(min-width: 768px)')
|
|
434
|
+
handleDesktop(matches: boolean) {
|
|
435
|
+
this.layout = matches ? 'desktop' : 'mobile';
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Avoid - manual window resize listening
|
|
439
|
+
// window.addEventListener('resize', () => {
|
|
440
|
+
// if (window.innerWidth >= 768) { /* ... */ }
|
|
441
|
+
// });
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## Lifecycle and Cleanup
|
|
445
|
+
|
|
446
|
+
All observers are automatically:
|
|
447
|
+
- Set up when element connects to DOM
|
|
448
|
+
- Cleaned up when element disconnects
|
|
449
|
+
- Re-established if element is moved in DOM
|
|
450
|
+
|
|
451
|
+
No manual cleanup is required - the framework handles everything:
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
@element('auto-cleanup')
|
|
455
|
+
class AutoCleanup extends HTMLElement {
|
|
456
|
+
// All observers are automatically managed
|
|
457
|
+
@observe('intersection')
|
|
458
|
+
handleIntersection(entry: IntersectionObserverEntry) { }
|
|
459
|
+
|
|
460
|
+
@observe('resize')
|
|
461
|
+
handleResize(entry: ResizeObserverEntry) { }
|
|
462
|
+
|
|
463
|
+
@observe('media:(min-width: 768px)')
|
|
464
|
+
handleMedia(matches: boolean) { }
|
|
465
|
+
|
|
466
|
+
// No cleanup code needed!
|
|
467
|
+
@render()
|
|
468
|
+
renderContent() {
|
|
469
|
+
return html`<div>Auto-cleanup content</div>`;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
## Performance Considerations
|
|
475
|
+
|
|
476
|
+
1. **Browser Support**: Observers check for API availability and warn if unsupported
|
|
477
|
+
2. **Shared Observers**: Media queries are cached globally
|
|
478
|
+
3. **Automatic Throttling**: Built-in throttle option prevents callback flooding
|
|
479
|
+
4. **Memory Management**: Proper cleanup prevents memory leaks
|
|
480
|
+
5. **Error Isolation**: Errors in one observer don't affect others
|
|
481
|
+
|
|
482
|
+
## Examples
|
|
483
|
+
|
|
484
|
+
### Virtual Scrolling
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
@element('virtual-list')
|
|
488
|
+
class VirtualList extends HTMLElement {
|
|
489
|
+
visibleItems = new Set<Element>();
|
|
490
|
+
|
|
491
|
+
@render()
|
|
492
|
+
renderContent() {
|
|
493
|
+
return html`<div class="viewport"></div>`;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
@observe('intersection', '.item', { rootMargin: '100px' })
|
|
497
|
+
handleItemVisibility(entry: IntersectionObserverEntry) {
|
|
498
|
+
if (entry.isIntersecting) {
|
|
499
|
+
this.visibleItems.add(entry.target);
|
|
500
|
+
this.renderItem(entry.target);
|
|
501
|
+
} else {
|
|
502
|
+
this.visibleItems.delete(entry.target);
|
|
503
|
+
this.unrenderItem(entry.target);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
renderItem(target: Element) {
|
|
508
|
+
// Render logic
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
unrenderItem(target: Element) {
|
|
512
|
+
// Unrender logic
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Responsive Dashboard
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
@element('dashboard')
|
|
521
|
+
class Dashboard extends HTMLElement {
|
|
522
|
+
columns = 1;
|
|
523
|
+
|
|
524
|
+
@observe('media:(min-width: 768px)')
|
|
525
|
+
handleTablet(matches: boolean) {
|
|
526
|
+
this.columns = matches ? 2 : 1;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
@observe('media:(min-width: 1024px)')
|
|
530
|
+
handleDesktop(matches: boolean) {
|
|
531
|
+
this.columns = matches ? 3 : this.columns;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
@observe('resize', { throttle: 200 })
|
|
535
|
+
handleResize(entry: ResizeObserverEntry) {
|
|
536
|
+
this.adjustCardSizes(entry.contentRect.width);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
@render()
|
|
540
|
+
renderContent() {
|
|
541
|
+
return html`<div class="dashboard">Dashboard with ${this.columns} columns</div>`;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
adjustCardSizes(width: number) {
|
|
545
|
+
// Adjust logic
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Form Auto-Save
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
@element('auto-save-form')
|
|
554
|
+
class AutoSaveForm extends HTMLElement {
|
|
555
|
+
@observe('mutation:attributes:value', 'input, textarea', { throttle: 1000 })
|
|
556
|
+
handleInputChange(mutations: MutationRecord[]) {
|
|
557
|
+
this.saveFormData();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
@observe('mutation:childList', '.dynamic-fields')
|
|
561
|
+
handleFieldsAdded(mutations: MutationRecord[]) {
|
|
562
|
+
mutations.forEach(mutation => {
|
|
563
|
+
mutation.addedNodes.forEach(node => {
|
|
564
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
565
|
+
this.initializeField(node as Element);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
@render()
|
|
572
|
+
renderContent() {
|
|
573
|
+
return html`
|
|
574
|
+
<form>
|
|
575
|
+
<div class="dynamic-fields"></div>
|
|
576
|
+
</form>
|
|
577
|
+
`;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
saveFormData() {
|
|
581
|
+
// Save logic
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
initializeField(field: Element) {
|
|
585
|
+
// Initialize logic
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
```
|