ngwr 13.0.0 → 14.0.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 +13 -6
- package/color-picker/styles/_index.scss +43 -5
- package/fesm2022/ngwr-alert.mjs +5 -5
- package/fesm2022/ngwr-alert.mjs.map +1 -1
- package/fesm2022/ngwr-back-top.mjs +11 -1
- package/fesm2022/ngwr-back-top.mjs.map +1 -1
- package/fesm2022/ngwr-blur-text.mjs +13 -2
- package/fesm2022/ngwr-blur-text.mjs.map +1 -1
- package/fesm2022/ngwr-border-glow.mjs +12 -3
- package/fesm2022/ngwr-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-breadcrumbs.mjs +14 -3
- package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
- package/fesm2022/ngwr-button.mjs +4 -4
- package/fesm2022/ngwr-button.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-testing.mjs +15 -0
- package/fesm2022/ngwr-calendar-testing.mjs.map +1 -1
- package/fesm2022/ngwr-calendar.mjs +51 -5
- package/fesm2022/ngwr-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-circular-text.mjs +20 -1
- package/fesm2022/ngwr-circular-text.mjs.map +1 -1
- package/fesm2022/ngwr-collapse.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker-testing.mjs +23 -7
- package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker.mjs +89 -10
- package/fesm2022/ngwr-color-picker.mjs.map +1 -1
- package/fesm2022/ngwr-command-palette.mjs +27 -3
- package/fesm2022/ngwr-command-palette.mjs.map +1 -1
- package/fesm2022/ngwr-context-menu.mjs +13 -3
- package/fesm2022/ngwr-context-menu.mjs.map +1 -1
- package/fesm2022/ngwr-date-adapters-fns.mjs +101 -33
- package/fesm2022/ngwr-date-adapters-fns.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker-testing.mjs +10 -2
- package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker.mjs +103 -13
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/ngwr-date.mjs +480 -79
- package/fesm2022/ngwr-date.mjs.map +1 -1
- package/fesm2022/ngwr-dialog.mjs +22 -16
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-directives.mjs +5 -2
- package/fesm2022/ngwr-directives.mjs.map +1 -1
- package/fesm2022/ngwr-drag-drop.mjs +30 -2
- package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +24 -18
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown.mjs +8 -3
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar.mjs +1 -1
- package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-file-upload.mjs +52 -12
- package/fesm2022/ngwr-file-upload.mjs.map +1 -1
- package/fesm2022/ngwr-fuzzy-text.mjs +21 -4
- package/fesm2022/ngwr-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-gradient-text.mjs +9 -2
- package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +63 -1
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +28 -1
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n.mjs +150 -22
- package/fesm2022/ngwr-i18n.mjs.map +1 -1
- package/fesm2022/ngwr-icon-adapters-feather.mjs +11 -0
- package/fesm2022/ngwr-icon-adapters-feather.mjs.map +1 -1
- package/fesm2022/ngwr-icon-adapters-lucide.mjs +22 -2
- package/fesm2022/ngwr-icon-adapters-lucide.mjs.map +1 -1
- package/fesm2022/ngwr-icon.mjs +474 -37
- package/fesm2022/ngwr-icon.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +3 -3
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-input-otp.mjs +21 -3
- package/fesm2022/ngwr-input-otp.mjs.map +1 -1
- package/fesm2022/ngwr-input.mjs +14 -7
- package/fesm2022/ngwr-input.mjs.map +1 -1
- package/fesm2022/ngwr-knob-testing.mjs.map +1 -1
- package/fesm2022/ngwr-knob.mjs +20 -2
- package/fesm2022/ngwr-knob.mjs.map +1 -1
- package/fesm2022/ngwr-loading-bar-router.mjs +73 -0
- package/fesm2022/ngwr-loading-bar-router.mjs.map +1 -0
- package/fesm2022/ngwr-loading-bar.mjs +21 -32
- package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
- package/fesm2022/ngwr-markdown.mjs +63 -11
- package/fesm2022/ngwr-markdown.mjs.map +1 -1
- package/fesm2022/ngwr-mention.mjs +104 -14
- package/fesm2022/ngwr-mention.mjs.map +1 -1
- package/fesm2022/ngwr-overlay.mjs +36 -7
- package/fesm2022/ngwr-overlay.mjs.map +1 -1
- package/fesm2022/ngwr-pagination-testing.mjs +11 -10
- package/fesm2022/ngwr-pagination-testing.mjs.map +1 -1
- package/fesm2022/ngwr-pagination.mjs +65 -30
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-popconfirm.mjs +3 -2
- package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
- package/fesm2022/ngwr-popover.mjs +2 -1
- package/fesm2022/ngwr-popover.mjs.map +1 -1
- package/fesm2022/ngwr-qr.mjs +38 -1
- package/fesm2022/ngwr-qr.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +93 -34
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text.mjs +10 -3
- package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
- package/fesm2022/ngwr-splitter-testing.mjs.map +1 -1
- package/fesm2022/ngwr-squircle.mjs +2 -1
- package/fesm2022/ngwr-squircle.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +35 -4
- package/fesm2022/ngwr-statistic.mjs.map +1 -1
- package/fesm2022/ngwr-table.mjs +35 -12
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-tabs-router.mjs +158 -0
- package/fesm2022/ngwr-tabs-router.mjs.map +1 -0
- package/fesm2022/ngwr-tabs-testing.mjs +13 -11
- package/fesm2022/ngwr-tabs-testing.mjs.map +1 -1
- package/fesm2022/ngwr-tabs.mjs +104 -12
- package/fesm2022/ngwr-tabs.mjs.map +1 -1
- package/fesm2022/ngwr-textarea.mjs +20 -1
- package/fesm2022/ngwr-textarea.mjs.map +1 -1
- package/fesm2022/ngwr-tour.mjs +1 -1
- package/fesm2022/ngwr-tour.mjs.map +1 -1
- package/fesm2022/ngwr-transfer.mjs +2 -2
- package/fesm2022/ngwr-transfer.mjs.map +1 -1
- package/fesm2022/ngwr-tree.mjs +18 -2
- package/fesm2022/ngwr-tree.mjs.map +1 -1
- package/fesm2022/ngwr-utils.mjs +143 -1
- package/fesm2022/ngwr-utils.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/fesm2022/ngwr-window-testing.mjs +10 -2
- package/fesm2022/ngwr-window-testing.mjs.map +1 -1
- package/fesm2022/ngwr-window.mjs +4 -4
- package/fesm2022/ngwr-window.mjs.map +1 -1
- package/i18n/en.json +33 -6
- package/i18n/ru.json +33 -6
- package/llms-full.txt +20 -11
- package/llms.txt +47 -16
- package/mcp/catalog.js +3 -3
- package/mcp/providers.js +11 -0
- package/package.json +11 -3
- package/schematics/migrations/v13/index.js +101 -0
- package/schematics/migrations/v14/index.js +422 -0
- package/schematics/migrations.json +10 -0
- package/schematics/page/templates.js +1 -1
- package/schematics/use/symbol-map.json +1 -0
- package/skills/ngwr/SKILL.md +4 -1
- package/skills/ngwr/references/catalog.md +2 -0
- package/skills/ngwr/references/setup.md +4 -0
- package/stepper/styles/_index.scss +19 -1
- package/textarea/styles/_index.scss +14 -6
- package/toast/styles/_index.scss +12 -4
- package/types/ngwr-alert.d.ts +3 -3
- package/types/ngwr-blur-text.d.ts +8 -0
- package/types/ngwr-breadcrumbs.d.ts +1 -0
- package/types/ngwr-button.d.ts +2 -2
- package/types/ngwr-calendar-testing.d.ts +21 -4
- package/types/ngwr-calendar.d.ts +37 -0
- package/types/ngwr-circular-text.d.ts +9 -0
- package/types/ngwr-collapse.d.ts +19 -15
- package/types/ngwr-color-picker-testing.d.ts +10 -4
- package/types/ngwr-color-picker.d.ts +70 -2
- package/types/ngwr-command-palette.d.ts +14 -0
- package/types/ngwr-context-menu.d.ts +1 -1
- package/types/ngwr-date-adapters-fns.d.ts +71 -29
- package/types/ngwr-date-picker-testing.d.ts +206 -185
- package/types/ngwr-date-picker.d.ts +7 -0
- package/types/ngwr-date.d.ts +143 -13
- package/types/ngwr-drag-drop.d.ts +19 -0
- package/types/ngwr-dropdown.d.ts +1 -1
- package/types/ngwr-event-calendar-testing.d.ts +12 -9
- package/types/ngwr-file-upload.d.ts +31 -3
- package/types/ngwr-fuzzy-text.d.ts +5 -0
- package/types/ngwr-i18n.d.ts +89 -7
- package/types/ngwr-icon-adapters-feather.d.ts +11 -0
- package/types/ngwr-icon.d.ts +42 -1
- package/types/ngwr-input-number.d.ts +1 -1
- package/types/ngwr-input-otp.d.ts +3 -0
- package/types/ngwr-input.d.ts +11 -4
- package/types/ngwr-knob-testing.d.ts +8 -4
- package/types/ngwr-knob.d.ts +18 -0
- package/types/ngwr-loading-bar-router.d.ts +35 -0
- package/types/ngwr-loading-bar.d.ts +19 -9
- package/types/ngwr-mention.d.ts +13 -0
- package/types/ngwr-pagination-testing.d.ts +11 -10
- package/types/ngwr-pagination.d.ts +39 -10
- package/types/ngwr-popconfirm.d.ts +1 -1
- package/types/ngwr-popover.d.ts +1 -1
- package/types/ngwr-select.d.ts +65 -10
- package/types/ngwr-shiny-text.d.ts +1 -0
- package/types/ngwr-splitter-testing.d.ts +9 -4
- package/types/ngwr-squircle.d.ts +1 -1
- package/types/ngwr-statistic.d.ts +22 -2
- package/types/ngwr-table.d.ts +24 -5
- package/types/ngwr-tabs-router.d.ts +67 -0
- package/types/ngwr-tabs-testing.d.ts +12 -10
- package/types/ngwr-tabs.d.ts +103 -6
- package/types/ngwr-textarea.d.ts +9 -0
- package/types/ngwr-tree.d.ts +7 -0
- package/types/ngwr-utils.d.ts +79 -1
- package/types/ngwr-version.d.ts +1 -1
- package/types/ngwr-window-testing.d.ts +11 -3
- package/types/ngwr-window.d.ts +11 -4
- package/window/styles/_index.scss +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ value controls implement `FormValueControl` / `FormCheckboxControl` themselves,
|
|
|
19
19
|
so `[formField]` writes the component's own `value` / `checked` model — there
|
|
20
20
|
is not one `ControlValueAccessor` in the library. Zoneless by construction, not
|
|
21
21
|
zoneless-compatible: signal inputs, signal state, `afterNextRender()` for DOM
|
|
22
|
-
work, and no `@NgModule` or `@Input()` decorator anywhere in the source.
|
|
22
|
+
work, and no `@NgModule` or `@Input()` decorator anywhere in the source. 204
|
|
23
23
|
tree-shakable entry points, on `@angular/cdk` for overlay, portal and a11y
|
|
24
24
|
primitives.
|
|
25
25
|
|
|
@@ -63,8 +63,11 @@ Classic `[(ngModel)]` and reactive forms still work — Angular 22 synthesises t
|
|
|
63
63
|
accessor for a signal-forms control — and every control is usable standalone
|
|
64
64
|
through its two-way `[(value)]` / `[(checked)]` model.
|
|
65
65
|
|
|
66
|
-
> **Status:** active development.
|
|
66
|
+
> **Status:** active development. v14 is the current major line (Angular 22 peer).
|
|
67
67
|
> Public API is stable across patch releases and still evolving between majors.
|
|
68
|
+
> Upgrading? `ng update ngwr@14` rewrites v14's five renames and reports the
|
|
69
|
+
> changes no codemod should guess at — the
|
|
70
|
+
> [migration guide](https://ngwr.dev/start/migration) walks every step.
|
|
68
71
|
> [Open an issue](https://github.com/thekhegay/ngwr/issues/new)
|
|
69
72
|
> if something breaks or feels wrong.
|
|
70
73
|
|
|
@@ -111,8 +114,12 @@ yarn add ngwr @angular/cdk
|
|
|
111
114
|
|
|
112
115
|
Beyond Angular itself, `@angular/cdk` and `@angular/forms` are the required
|
|
113
116
|
peers — forms because the value controls implement its Signal Forms interfaces.
|
|
114
|
-
`@angular/router` is optional,
|
|
115
|
-
|
|
117
|
+
`@angular/router` is optional, and since v14 nothing pulls it in unless you ask:
|
|
118
|
+
a `<wr-tab routerLink>` needs `WrTabsRouting` from `ngwr/tabs/router` on the
|
|
119
|
+
strip, and `<wr-loading-bar>` follows navigation only once you add
|
|
120
|
+
`provideWrLoadingBarRouter()` from `ngwr/loading-bar/router`. Both used to be
|
|
121
|
+
automatic, which cost every app 66–76 kB of router whether it routed or not.
|
|
122
|
+
A stock `ng new` app already ships forms and router, so
|
|
116
123
|
`@angular/cdk` is the only one you have to add — which is why it is on the
|
|
117
124
|
install lines above. Add an icon set and a date library only if you use them —
|
|
118
125
|
`lucide` (or `feather-icons`) for the icon adapters, and `date-fns` or `luxon`
|
|
@@ -314,7 +321,7 @@ Math (`clamp`, `round`), coercion (`numAttr`), css helpers (`resolveCssSize`, `g
|
|
|
314
321
|
- **CDK-powered.** Overlays, portals, and a11y come from `@angular/cdk`. We add `provideWrOverlay()` so NGWR overlays never collide with other CDK consumers (Material, NG-ZORRO, etc.).
|
|
315
322
|
- **Mobile & responsive.** Overlays collapse to bottom-sheets on small screens (`provideWrResponsiveOverlays()`), touch targets grow to ≥44px on coarse pointers, a `touch` density preset enlarges the nine control families that read the multipliers, and drawer / lightbox / toast / carousel respond to swipe gestures. Fixed surfaces respect `env(safe-area-inset-*)`, and layout components (`descriptions`, `stepper`, `page-header`, `toolbar`, `pagination`, `table`) reflow to their container via container queries. [Guide](https://ngwr.dev/guides/mobile).
|
|
316
323
|
- **Table, batteries included.** `wr-table` covers column pinning / resizing / drag-reorder, row selection, expandable rows, grouping, tree rows (`childrenKey` — the forest flattens into the same `<tbody>`, so pinning and cell templates keep working at every depth, and the table announces a `treegrid`), summary rows, CSV export (`exportCsv()`, dependency-free RFC 4180) and a virtualized body — all opt-in inputs on the one component. Excel (`.xlsx`) export is deliberately not shipped: it would mean a third-party dependency.
|
|
317
|
-
- **Tree-shakable.**
|
|
324
|
+
- **Tree-shakable.** 204 separate ng-packagr entry points — import only what you use. Per-component FESM bundles are small: a median of ~4 KB gzipped, the heaviest (`ngwr/markdown`) ~23 KB. Every runtime bundle together gzips to ~690 KB — the 70 `ngwr/<name>/testing` harnesses aside, since they never reach an app bundle — but real apps pull a handful of entries. The only runtime dependency is `tslib`.
|
|
318
325
|
- **Modular SCSS.** Component styles are scoped through CSS custom properties. Theme tokens live in `ngwr/theme`; utilities (`grid`, `reset`) and the breakpoints SCSS API are opt-in.
|
|
319
326
|
- **Tree-shaken icons.** `provideWrIcons(lucideIcons({ plus: Plus }))` registers only the icons you actually import. Dev-mode validation warns about unregistered icons.
|
|
320
327
|
- **Reactbits ports, dependency-free.** All animation ports are reimplemented with vanilla DOM + Web Animations API / `IntersectionObserver` / `requestAnimationFrame` / raw WebGL — no GSAP, no `motion/react`, no `matter-js`, no `ogl`.
|
|
@@ -345,7 +352,7 @@ pnpm dev # ng serve --o (showcase)
|
|
|
345
352
|
pnpm test # ng test lib (vitest)
|
|
346
353
|
pnpm build:lib # ng build lib + ai assets + dist assets + i18n json + schematics + mcp server
|
|
347
354
|
pnpm build:showcase # ai assets + showcase build + sitemap + markdown twins
|
|
348
|
-
pnpm lint # ng lint + eslint scripts + stylelint + colour parity + rtl
|
|
355
|
+
pnpm lint # ng lint + eslint scripts + stylelint + colour parity + rtl + registry + tokens
|
|
349
356
|
```
|
|
350
357
|
|
|
351
358
|
## Authors
|
|
@@ -65,6 +65,11 @@
|
|
|
65
65
|
@include theme.touch-target($positioned: true);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// A gradient has no logical direction keyword, so both tracks are painted
|
|
69
|
+
// physically and mirrored by hand. They are one-value sliders — the thumb runs
|
|
70
|
+
// on `inset-inline-start` and the drag is measured from the track's inline
|
|
71
|
+
// start — so 0° / transparent has to sit at the READER's start, which is the
|
|
72
|
+
// physical right edge under `dir="rtl"`.
|
|
68
73
|
&__slider--hue {
|
|
69
74
|
background: linear-gradient(
|
|
70
75
|
to right,
|
|
@@ -76,6 +81,19 @@
|
|
|
76
81
|
hsl(300, 100%, 50%),
|
|
77
82
|
hsl(360, 100%, 50%)
|
|
78
83
|
);
|
|
84
|
+
|
|
85
|
+
[dir='rtl'] & {
|
|
86
|
+
background: linear-gradient(
|
|
87
|
+
to left,
|
|
88
|
+
hsl(0, 100%, 50%),
|
|
89
|
+
hsl(60, 100%, 50%),
|
|
90
|
+
hsl(120, 100%, 50%),
|
|
91
|
+
hsl(180, 100%, 50%),
|
|
92
|
+
hsl(240, 100%, 50%),
|
|
93
|
+
hsl(300, 100%, 50%),
|
|
94
|
+
hsl(360, 100%, 50%)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
79
97
|
}
|
|
80
98
|
|
|
81
99
|
&__slider--alpha {
|
|
@@ -85,6 +103,14 @@
|
|
|
85
103
|
background-size:
|
|
86
104
|
auto,
|
|
87
105
|
8px 8px;
|
|
106
|
+
|
|
107
|
+
// Only the alpha ramp flips; the chequerboard behind it is a texture with no
|
|
108
|
+
// direction, and re-declaring it would be a second copy to keep in step.
|
|
109
|
+
[dir='rtl'] & {
|
|
110
|
+
background-image:
|
|
111
|
+
linear-gradient(to left, rgba(var(--wr-color-picker-rgb), 0), rgba(var(--wr-color-picker-rgb), 1)),
|
|
112
|
+
conic-gradient(#cdd2dd 25%, #fff 0 50%, #cdd2dd 0 75%, #fff 0);
|
|
113
|
+
}
|
|
88
114
|
}
|
|
89
115
|
|
|
90
116
|
// Thumb
|
|
@@ -94,10 +120,10 @@
|
|
|
94
120
|
height: var(--wr-color-picker-thumb-size);
|
|
95
121
|
transform: translate(-50%, -50%);
|
|
96
122
|
top: 0;
|
|
97
|
-
// The template overrides
|
|
98
|
-
// from `(event.clientX - rect.left) / rect.width`
|
|
99
|
-
//
|
|
100
|
-
//
|
|
123
|
+
// The SV thumb's own fallback: the template overrides it with `[style.left.%]`,
|
|
124
|
+
// computed from `(event.clientX - rect.left) / rect.width` against a canvas
|
|
125
|
+
// that is a picture rather than a track and so does not mirror. The two
|
|
126
|
+
// SLIDER thumbs take the logical inset below instead.
|
|
101
127
|
// rtl-ok: canvas geometry in physical pixels, centred by the physical translate
|
|
102
128
|
left: 0;
|
|
103
129
|
border-radius: 50%;
|
|
@@ -109,9 +135,21 @@
|
|
|
109
135
|
background: transparent;
|
|
110
136
|
}
|
|
111
137
|
|
|
112
|
-
// Sliders position their thumb at vertical centre
|
|
138
|
+
// Sliders position their thumb at vertical centre — and on the INLINE axis,
|
|
139
|
+
// because a one-value track mirrors. `left: auto` first: the shared rule above
|
|
140
|
+
// sets `left: 0` for the SV thumb, and leaving it standing beside the RTL
|
|
141
|
+
// `right` the template writes would over-constrain the box.
|
|
113
142
|
&__thumb--slider {
|
|
114
143
|
top: 50%;
|
|
144
|
+
left: auto;
|
|
145
|
+
inset-inline-start: 0;
|
|
146
|
+
|
|
147
|
+
// The inline half of the centring pair follows the inset it centres — the
|
|
148
|
+
// same rule `wr-slider`'s thumb states at length. The block half is
|
|
149
|
+
// symmetric and stays put.
|
|
150
|
+
[dir='rtl'] & {
|
|
151
|
+
transform: translate(50%, -50%);
|
|
152
|
+
}
|
|
115
153
|
}
|
|
116
154
|
|
|
117
155
|
// Preview tile
|
package/fesm2022/ngwr-alert.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import { WrIcon } from 'ngwr/icon';
|
|
|
16
16
|
* @example
|
|
17
17
|
* ```html
|
|
18
18
|
* <wr-alert title="Saved" message="Your changes are live." type="success" />
|
|
19
|
-
* <wr-alert title="Failed" type="danger"
|
|
19
|
+
* <wr-alert title="Failed" type="danger" closable (closed)="onClose()" />
|
|
20
20
|
* ```
|
|
21
21
|
*
|
|
22
22
|
* @see https://ngwr.dev/reference/components/alert
|
|
@@ -66,7 +66,7 @@ class WrAlert {
|
|
|
66
66
|
*
|
|
67
67
|
* @default false
|
|
68
68
|
*/
|
|
69
|
-
|
|
69
|
+
closable = input(false, { ...(ngDevMode ? { debugName: "closable" } : /* istanbul ignore next */ {}), transform: coerceBooleanProperty });
|
|
70
70
|
/**
|
|
71
71
|
* Emitted when the user dismisses the alert via the close button.
|
|
72
72
|
*/
|
|
@@ -90,7 +90,7 @@ class WrAlert {
|
|
|
90
90
|
this.closed.emit();
|
|
91
91
|
}
|
|
92
92
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrAlert, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
93
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrAlert, isStandalone: true, selector: "wr-alert", inputs: { closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, iconName: { classPropertyName: "iconName", publicName: "iconName", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null },
|
|
93
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrAlert, isStandalone: true, selector: "wr-alert", inputs: { closeLabel: { classPropertyName: "closeLabel", publicName: "closeLabel", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, iconName: { classPropertyName: "iconName", publicName: "iconName", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, closable: { classPropertyName: "closable", publicName: "closable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { properties: { "class": "classes()", "attr.role": "liveRole()", "attr.aria-live": "liveLevel()" } }, ngImport: i0, template: "@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n @case ('danger') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n", dependencies: [{ kind: "component", type: WrIcon, selector: "wr-icon", inputs: ["name"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
94
94
|
}
|
|
95
95
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrAlert, decorators: [{
|
|
96
96
|
type: Component,
|
|
@@ -98,8 +98,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
98
98
|
'[class]': 'classes()',
|
|
99
99
|
'[attr.role]': 'liveRole()',
|
|
100
100
|
'[attr.aria-live]': 'liveLevel()',
|
|
101
|
-
}, template: "@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n @case ('danger') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n </div>\n\n @if (
|
|
102
|
-
}], propDecorators: { closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], iconName: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconName", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }],
|
|
101
|
+
}, template: "@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n @case ('danger') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n" }]
|
|
102
|
+
}], propDecorators: { closeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeLabel", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], iconName: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconName", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], closable: [{ type: i0.Input, args: [{ isSignal: true, alias: "closable", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Generated bundle index. Do not edit.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-alert.mjs","sources":["../../../projects/lib/alert/alert.ts","../../../projects/lib/alert/alert.html","../../../projects/lib/alert/ngwr-alert.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, computed, input, output, signal } from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrIcon, type WrIconName } from 'ngwr/icon';\n\nimport type { WrAlertType } from './interfaces';\n\n/**\n * Inline status banner. Use for feedback messages — saved/failed/notice etc.\n *\n * @example\n * ```html\n * <wr-alert title=\"Saved\" message=\"Your changes are live.\" type=\"success\" />\n * <wr-alert title=\"Failed\" type=\"danger\"
|
|
1
|
+
{"version":3,"file":"ngwr-alert.mjs","sources":["../../../projects/lib/alert/alert.ts","../../../projects/lib/alert/alert.html","../../../projects/lib/alert/ngwr-alert.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, computed, input, output, signal } from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrIcon, type WrIconName } from 'ngwr/icon';\n\nimport type { WrAlertType } from './interfaces';\n\n/**\n * Inline status banner. Use for feedback messages — saved/failed/notice etc.\n *\n * @example\n * ```html\n * <wr-alert title=\"Saved\" message=\"Your changes are live.\" type=\"success\" />\n * <wr-alert title=\"Failed\" type=\"danger\" closable (closed)=\"onClose()\" />\n * ```\n *\n * @see https://ngwr.dev/reference/components/alert\n */\n@Component({\n selector: 'wr-alert',\n templateUrl: './alert.html',\n imports: [WrIcon],\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[attr.role]': 'liveRole()',\n '[attr.aria-live]': 'liveLevel()',\n },\n})\nexport class WrAlert {\n /** Accessible name. Falls back to `alert.close`, then `'Close alert'`. */\n readonly closeLabel = input<string | null>(null);\n\n protected readonly resolvedCloseLabel = useI18nText(this.closeLabel, 'alert.close', 'Close alert');\n\n /**\n * Optional headline shown at the top of the alert.\n *\n * @default null\n */\n readonly title = input<string | null>(null);\n\n /**\n * Visual variant.\n *\n * @default 'info'\n */\n readonly type = input<WrAlertType>('info');\n\n /**\n * Override the default per-type icon with any ngwr icon name.\n *\n * @default null\n */\n readonly iconName = input<WrIconName | null>(null);\n\n /**\n * Optional secondary message rendered below the title.\n *\n * @default null\n */\n readonly message = input<string | null>(null);\n\n /**\n * When `true`, renders a leading status icon matching the `type`.\n * Pass `false` to hide. Ignored when `iconName` is set.\n *\n * @default true\n */\n readonly icon = input(true, { transform: coerceBooleanProperty });\n\n /**\n * When `true`, renders a close button.\n *\n * @default false\n */\n readonly closable = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Emitted when the user dismisses the alert via the close button.\n */\n readonly closed = output<void>();\n\n protected readonly dismissed = signal(false);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-alert', `wr-alert--${this.type()}`];\n if (this.dismissed()) parts.push('wr-alert--dismissed');\n return parts.join(' ');\n });\n\n /** Escalate role/aria-live for danger/warning alerts. */\n protected readonly liveRole = computed(() =>\n this.dismissed() ? null : this.type() === 'danger' ? 'alert' : 'status'\n );\n protected readonly liveLevel = computed(() =>\n this.dismissed() ? null : this.type() === 'danger' || this.type() === 'warning' ? 'assertive' : 'polite'\n );\n\n protected onClose(): void {\n this.dismissed.set(true);\n this.closed.emit();\n }\n}\n","@if (!dismissed()) {\n @if (iconName(); as n) {\n <wr-icon class=\"wr-alert__icon\" [name]=\"n\" />\n } @else if (icon()) {\n <svg\n class=\"wr-alert__icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n @switch (type()) {\n @case ('success') {\n <path d=\"M21.801 10A10 10 0 1 1 17 3.335\" />\n <path d=\"m9 11 3 3L22 4\" />\n }\n @case ('warning') {\n <path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\" />\n <path d=\"M12 9v4\" />\n <path d=\"M12 17h.01\" />\n }\n @case ('danger') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n @case ('neutral') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n }\n @case ('offline') {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"m4.9 4.9 14.2 14.2\" />\n }\n @default {\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 16v-4\" />\n <path d=\"M12 8h.01\" />\n }\n }\n </svg>\n }\n\n <div class=\"wr-alert__body\">\n @if (title(); as t) {\n <p class=\"wr-alert__title\">{{ t }}</p>\n @if (message(); as m) {\n <p class=\"wr-alert__message\">{{ m }}</p>\n }\n } @else if (message(); as m) {\n <p class=\"wr-alert__title\">{{ m }}</p>\n }\n </div>\n\n @if (closable()) {\n <button type=\"button\" class=\"wr-alert__close\" [attr.aria-label]=\"resolvedCloseLabel()\" (click)=\"onClose()\">\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </button>\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -57,7 +57,17 @@ class WrBackTop {
|
|
|
57
57
|
this.zone.run(() => this.visible.set(should));
|
|
58
58
|
};
|
|
59
59
|
this.zone.runOutsideAngular(() => window.addEventListener('scroll', handler, { passive: true }));
|
|
60
|
-
// Initial check (page might already be scrolled)
|
|
60
|
+
// Initial check (page might already be scrolled), and it is SYNCHRONOUS on
|
|
61
|
+
// purpose. `window.scrollY` forces a style + layout flush, which is what
|
|
62
|
+
// makes constructing N of these quadratic — 1000 instances cost 90ms and
|
|
63
|
+
// 5000 cost 1640ms, of which ~95% is the flush: replacing `scrollY` with a
|
|
64
|
+
// plain property drops 5000 to 83ms with all 5000 scroll listeners still
|
|
65
|
+
// registered, so the per-instance listener is not the cost it looks like.
|
|
66
|
+
// The bulk figure is nobody's bill — a page has one back-to-top button, and
|
|
67
|
+
// one instance measures 0.03ms over an equivalent component. Deferring to
|
|
68
|
+
// `afterNextRender` would land the signal a change-detection pass later, so
|
|
69
|
+
// a page restored part way down would fade the button in from `opacity: 0`
|
|
70
|
+
// instead of painting it in place. That first paint is the contract.
|
|
61
71
|
handler();
|
|
62
72
|
this.destroyRef.onDestroy(() => window.removeEventListener('scroll', handler));
|
|
63
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-back-top.mjs","sources":["../../../projects/lib/back-top/back-top.ts","../../../projects/lib/back-top/back-top.html","../../../projects/lib/back-top/ngwr-back-top.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceNumberProperty } from '@angular/cdk/coercion';\nimport { Component, DestroyRef, NgZone, ViewEncapsulation, computed, inject, input, signal } from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrPlatform } from 'ngwr/platform';\nimport { WrScroll } from 'ngwr/scroll';\n\n/**\n * Pinned floating button that scrolls the page back to the top. Appears\n * once the user has scrolled past `visibilityThreshold` pixels.\n *\n * @example\n * ```html\n * <wr-back-top />\n *\n * <wr-back-top visibilityThreshold=\"600\" [offset]=\"80\">\n * <svg>…custom icon…</svg>\n * </wr-back-top>\n * ```\n *\n * @see https://ngwr.dev/reference/components/back-top\n */\n@Component({\n selector: 'wr-back-top',\n templateUrl: './back-top.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[attr.aria-hidden]': '!visible()',\n // `aria-hidden` alone left a 44px button that focus still landed on: invisible\n // (opacity 0) but in the tab order, and unannounceable once it got there.\n // `inert` is what actually takes it out of both.\n '[attr.inert]': \"visible() ? null : ''\",\n },\n})\nexport class WrBackTop {\n /** Show the button once `window.scrollY` exceeds this many pixels. @default 400 */\n readonly visibilityThreshold = input(400, {\n transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 400)),\n });\n\n /** Pixel offset subtracted from the scroll target — for sticky headers. @default 0 */\n readonly offset = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n\n /** Accessible label for the button. Falls back to `backTop.label`, then `'Back to top'`. */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'backTop.label', 'Back to top');\n\n protected readonly visible = signal(false);\n\n private readonly scroll = inject(WrScroll);\n private readonly platform = inject(WrPlatform);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-back-top'];\n if (this.visible()) parts.push('wr-back-top--visible');\n return parts.join(' ');\n });\n\n constructor() {\n if (!this.platform.isBrowser) return;\n const handler = (): void => {\n const should = window.scrollY > this.visibilityThreshold();\n if (should !== this.visible()) this.zone.run(() => this.visible.set(should));\n };\n this.zone.runOutsideAngular(() => window.addEventListener('scroll', handler, { passive: true }));\n // Initial check (page might already be scrolled).\n handler();\n this.destroyRef.onDestroy(() => window.removeEventListener('scroll', handler));\n }\n\n protected onClick(): void {\n this.scroll.toTop({ offset: this.offset() });\n }\n}\n","<button type=\"button\" class=\"wr-back-top__button\" [attr.aria-label]=\"resolvedAriaLabel()\" (click)=\"onClick()\">\n <!-- The arrow is FALLBACK content: rendered next to a projected icon rather\n than instead of it, the documented custom-icon example put two glyphs in\n one 44px circle. -->\n <ng-content>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" aria-hidden=\"true\">\n <path\n d=\"M9 4 L9 14 M4 9 L9 4 L14 9\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </ng-content>\n</button>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;AAAA;;;;;AAKG;AASH;;;;;;;;;;;;;;AAcG;MAcU,SAAS,CAAA;;IAEX,mBAAmB,GAAG,KAAK,CAAC,GAAG,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,qBAAA,EAAA,8BAAA,EAAA,CAAA,EACtC,SAAS,EAAE,CAAC,CAAU,KAAa,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAA,CAC5E;;IAGO,MAAM,GAAG,KAAK,CAAC,CAAC,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,QAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,CAAC,CAAU,KAAa,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAA,CAAG;;IAGjG,SAAS,GAAG,KAAK,CAAgB,IAAI;kFAAC;IAE5B,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,EAAE,aAAa,CAAC;IAE/E,OAAO,GAAG,MAAM,CAAC,KAAK;gFAAC;AAEzB,IAAA,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC;AACzB,IAAA,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC;AAC7B,IAAA,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC;AACrB,IAAA,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AAE7B,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAK;AACzC,QAAA,MAAM,KAAK,GAAG,CAAC,aAAa,CAAC;QAC7B,IAAI,IAAI,CAAC,OAAO,EAAE;AAAE,YAAA,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC;AACtD,QAAA,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;IACxB,CAAC;gFAAC;AAEF,IAAA,WAAA,GAAA;AACE,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS;YAAE;QAC9B,MAAM,OAAO,GAAG,MAAW;YACzB,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,mBAAmB,EAAE;AAC1D,YAAA,IAAI,MAAM,KAAK,IAAI,CAAC,OAAO,EAAE;AAAE,gBAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC9E,QAAA,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC
|
|
1
|
+
{"version":3,"file":"ngwr-back-top.mjs","sources":["../../../projects/lib/back-top/back-top.ts","../../../projects/lib/back-top/back-top.html","../../../projects/lib/back-top/ngwr-back-top.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceNumberProperty } from '@angular/cdk/coercion';\nimport { Component, DestroyRef, NgZone, ViewEncapsulation, computed, inject, input, signal } from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrPlatform } from 'ngwr/platform';\nimport { WrScroll } from 'ngwr/scroll';\n\n/**\n * Pinned floating button that scrolls the page back to the top. Appears\n * once the user has scrolled past `visibilityThreshold` pixels.\n *\n * @example\n * ```html\n * <wr-back-top />\n *\n * <wr-back-top visibilityThreshold=\"600\" [offset]=\"80\">\n * <svg>…custom icon…</svg>\n * </wr-back-top>\n * ```\n *\n * @see https://ngwr.dev/reference/components/back-top\n */\n@Component({\n selector: 'wr-back-top',\n templateUrl: './back-top.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[attr.aria-hidden]': '!visible()',\n // `aria-hidden` alone left a 44px button that focus still landed on: invisible\n // (opacity 0) but in the tab order, and unannounceable once it got there.\n // `inert` is what actually takes it out of both.\n '[attr.inert]': \"visible() ? null : ''\",\n },\n})\nexport class WrBackTop {\n /** Show the button once `window.scrollY` exceeds this many pixels. @default 400 */\n readonly visibilityThreshold = input(400, {\n transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 400)),\n });\n\n /** Pixel offset subtracted from the scroll target — for sticky headers. @default 0 */\n readonly offset = input(0, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 0)) });\n\n /** Accessible label for the button. Falls back to `backTop.label`, then `'Back to top'`. */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'backTop.label', 'Back to top');\n\n protected readonly visible = signal(false);\n\n private readonly scroll = inject(WrScroll);\n private readonly platform = inject(WrPlatform);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n\n protected readonly classes = computed(() => {\n const parts = ['wr-back-top'];\n if (this.visible()) parts.push('wr-back-top--visible');\n return parts.join(' ');\n });\n\n constructor() {\n if (!this.platform.isBrowser) return;\n const handler = (): void => {\n const should = window.scrollY > this.visibilityThreshold();\n if (should !== this.visible()) this.zone.run(() => this.visible.set(should));\n };\n this.zone.runOutsideAngular(() => window.addEventListener('scroll', handler, { passive: true }));\n // Initial check (page might already be scrolled), and it is SYNCHRONOUS on\n // purpose. `window.scrollY` forces a style + layout flush, which is what\n // makes constructing N of these quadratic — 1000 instances cost 90ms and\n // 5000 cost 1640ms, of which ~95% is the flush: replacing `scrollY` with a\n // plain property drops 5000 to 83ms with all 5000 scroll listeners still\n // registered, so the per-instance listener is not the cost it looks like.\n // The bulk figure is nobody's bill — a page has one back-to-top button, and\n // one instance measures 0.03ms over an equivalent component. Deferring to\n // `afterNextRender` would land the signal a change-detection pass later, so\n // a page restored part way down would fade the button in from `opacity: 0`\n // instead of painting it in place. That first paint is the contract.\n handler();\n this.destroyRef.onDestroy(() => window.removeEventListener('scroll', handler));\n }\n\n protected onClick(): void {\n this.scroll.toTop({ offset: this.offset() });\n }\n}\n","<button type=\"button\" class=\"wr-back-top__button\" [attr.aria-label]=\"resolvedAriaLabel()\" (click)=\"onClick()\">\n <!-- The arrow is FALLBACK content: rendered next to a projected icon rather\n than instead of it, the documented custom-icon example put two glyphs in\n one 44px circle. -->\n <ng-content>\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" aria-hidden=\"true\">\n <path\n d=\"M9 4 L9 14 M4 9 L9 4 L14 9\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </ng-content>\n</button>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;AAAA;;;;;AAKG;AASH;;;;;;;;;;;;;;AAcG;MAcU,SAAS,CAAA;;IAEX,mBAAmB,GAAG,KAAK,CAAC,GAAG,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,qBAAA,EAAA,8BAAA,EAAA,CAAA,EACtC,SAAS,EAAE,CAAC,CAAU,KAAa,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAA,CAC5E;;IAGO,MAAM,GAAG,KAAK,CAAC,CAAC,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,QAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,CAAC,CAAU,KAAa,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAA,CAAG;;IAGjG,SAAS,GAAG,KAAK,CAAgB,IAAI;kFAAC;IAE5B,iBAAiB,GAAG,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,EAAE,aAAa,CAAC;IAE/E,OAAO,GAAG,MAAM,CAAC,KAAK;gFAAC;AAEzB,IAAA,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC;AACzB,IAAA,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC;AAC7B,IAAA,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC;AACrB,IAAA,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AAE7B,IAAA,OAAO,GAAG,QAAQ,CAAC,MAAK;AACzC,QAAA,MAAM,KAAK,GAAG,CAAC,aAAa,CAAC;QAC7B,IAAI,IAAI,CAAC,OAAO,EAAE;AAAE,YAAA,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC;AACtD,QAAA,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;IACxB,CAAC;gFAAC;AAEF,IAAA,WAAA,GAAA;AACE,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS;YAAE;QAC9B,MAAM,OAAO,GAAG,MAAW;YACzB,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC,mBAAmB,EAAE;AAC1D,YAAA,IAAI,MAAM,KAAK,IAAI,CAAC,OAAO,EAAE;AAAE,gBAAA,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC9E,QAAA,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;;;;;;;;;;;;AAYhG,QAAA,OAAO,EAAE;AACT,QAAA,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChF;IAEU,OAAO,GAAA;AACf,QAAA,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;IAC9C;uGAnDW,SAAS,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAT,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,SAAS,6mBC1CtB,2pBAiBA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FDyBa,SAAS,EAAA,UAAA,EAAA,CAAA;kBAbrB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,aAAa,EAAA,aAAA,EAER,iBAAiB,CAAC,IAAI,EAAA,IAAA,EAC/B;AACJ,wBAAA,SAAS,EAAE,WAAW;AACtB,wBAAA,oBAAoB,EAAE,YAAY;;;;AAIlC,wBAAA,cAAc,EAAE,uBAAuB;AACxC,qBAAA,EAAA,QAAA,EAAA,2pBAAA,EAAA;;;AExCH;;AAEG;;;;"}
|
|
@@ -92,10 +92,21 @@ class WrBlurText {
|
|
|
92
92
|
* watching the same host and all of them able to fire `animate()`.
|
|
93
93
|
*/
|
|
94
94
|
observer = null;
|
|
95
|
+
/**
|
|
96
|
+
* The effect below reaches `startObserver()` through a `queueMicrotask`, so
|
|
97
|
+
* the component can be destroyed between the two. The destroy hook runs first
|
|
98
|
+
* and disconnects whatever exists at that moment — then the continuation
|
|
99
|
+
* builds a FRESH observer on a host Angular has already detached, and nothing
|
|
100
|
+
* is left to disconnect it. Same hazard, and same flag, as `wr-split-text`.
|
|
101
|
+
*/
|
|
102
|
+
destroyed = false;
|
|
95
103
|
constructor() {
|
|
96
104
|
if (!this.isBrowser)
|
|
97
105
|
return;
|
|
98
|
-
this.destroyRef.onDestroy(() =>
|
|
106
|
+
this.destroyRef.onDestroy(() => {
|
|
107
|
+
this.destroyed = true;
|
|
108
|
+
this.observer?.disconnect();
|
|
109
|
+
});
|
|
99
110
|
afterNextRender(() => this.startObserver());
|
|
100
111
|
effect(() => {
|
|
101
112
|
this.text();
|
|
@@ -105,7 +116,7 @@ class WrBlurText {
|
|
|
105
116
|
});
|
|
106
117
|
}
|
|
107
118
|
startObserver() {
|
|
108
|
-
if (this.hasAnimated)
|
|
119
|
+
if (this.destroyed || this.hasAnimated)
|
|
109
120
|
return;
|
|
110
121
|
const host = this.host.nativeElement;
|
|
111
122
|
this.observer?.disconnect();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-blur-text.mjs","sources":["../../../projects/lib/blur-text/blur-text.ts","../../../projects/lib/blur-text/blur-text.html","../../../projects/lib/blur-text/ngwr-blur-text.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Angular adaptation of the BlurText effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/text-animations/blur-text\n *\n * The reactbits version uses `motion/react` (framer-motion successor).\n * This port is dependency-free — uses the Web Animations API for the\n * three-keyframe blur-and-fade-in, triggered by `IntersectionObserver`.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n output,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrBlurTextDirection, WrBlurTextUnit } from './interfaces';\n\ntype Piece = { readonly kind: 'piece'; readonly text: string } | { readonly kind: 'space'; readonly text: string };\n\nfunction splitPieces(text: string, unit: WrBlurTextUnit): readonly Piece[] {\n if (!text) return [];\n if (unit === 'words') {\n return text.split(/(\\s+)/).flatMap<Piece>(seg => {\n if (seg.length === 0) return [];\n if (/^\\s+$/.test(seg)) return [{ kind: 'space', text: seg }];\n return [{ kind: 'piece', text: seg }];\n });\n }\n return [...text].map<Piece>(ch => (/\\s/.test(ch) ? { kind: 'space', text: ch } : { kind: 'piece', text: ch }));\n}\n\n/**\n * Reveals text by splitting it into chars or words and animating each\n * piece in from a blurred / offset state through a brief mid-step into\n * the steady state. Triggers when the host enters the viewport.\n *\n * @example\n * ```html\n * <wr-blur-text\n * text=\"Welcome to ngwr\"\n * animateBy=\"words\"\n * direction=\"top\"\n * [delay]=\"120\"\n * [stepDuration]=\"0.4\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/text-animations/blur-text\n */\n@Component({\n selector: 'wr-blur-text',\n templateUrl: './blur-text.html',\n styleUrl: './blur-text.scss',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-blur-text' },\n})\nexport class WrBlurText {\n /** Text to animate. Required. */\n readonly text = input.required<string>();\n\n /** Split granularity. @default 'words' */\n readonly animateBy = input<WrBlurTextUnit>('words');\n\n /** Entry direction. `'top'` slides down into place; `'bottom'` slides up. @default 'top' */\n readonly direction = input<WrBlurTextDirection>('top');\n\n /** Per-piece stagger in ms. @default 200 */\n readonly delay = input(200, { transform: numAttr(200) });\n\n /** Duration of each keyframe step in seconds (total = 2 × stepDuration). @default 0.35 */\n readonly stepDuration = input(0.35, { transform: numAttr(0.35) });\n\n /** CSS easing function. @default 'linear' (matches reactbits' identity easing) */\n readonly easing = input('linear');\n\n /** `IntersectionObserver.threshold` (0..1). @default 0.1 */\n readonly threshold = input(0.1, { transform: numAttr(0.1) });\n\n /** `IntersectionObserver.rootMargin`. @default '0px' */\n readonly rootMargin = input('0px');\n\n /** Emitted once all pieces finish animating. */\n readonly animationComplete = output<void>();\n\n protected readonly pieces = computed(() => splitPieces(this.text(), this.animateBy()));\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly platform = inject(WrPlatform);\n\n private hasAnimated = false;\n\n /**\n * The ONE observer, replaced rather than stacked.\n *\n * `startObserver()` used to `new IntersectionObserver(…)` and register its own\n * `destroyRef.onDestroy` every time, and the effect below calls it on every\n * `text` change after setting `hasAnimated = false` — so the guard at the top\n * of `startObserver` never blocked that path. An off-screen element (the only\n * case where the observer does not immediately disconnect itself) therefore\n * leaked one live observer AND one destroy callback per change, all of them\n * watching the same host and all of them able to fire `animate()`.\n */\n private observer: IntersectionObserver | null = null;\n\n constructor() {\n if (!this.isBrowser) return;\n this.destroyRef.onDestroy(() => this.observer?.disconnect());\n afterNextRender(() => this.startObserver());\n effect(() => {\n this.text();\n this.animateBy();\n this.hasAnimated = false;\n queueMicrotask(() => this.startObserver());\n });\n }\n\n private startObserver(): void {\n if (this.hasAnimated) return;\n const host = this.host.nativeElement;\n this.observer?.disconnect();\n const io = new IntersectionObserver(\n (entries, obs) => {\n for (const e of entries) {\n if (e.isIntersecting) {\n obs.disconnect();\n this.animate();\n break;\n }\n }\n },\n { threshold: this.threshold(), rootMargin: this.rootMargin() }\n );\n this.observer = io;\n io.observe(host);\n }\n\n private animate(): void {\n if (this.hasAnimated) return;\n this.hasAnimated = true;\n\n // Reduced motion: pieces already render clear and in place — skip\n // the blur tween and report completion right away.\n if (this.platform.prefersReducedMotion()) {\n this.animationComplete.emit();\n return;\n }\n\n const targets = this.host.nativeElement.querySelectorAll<HTMLElement>('.wr-blur-text__piece');\n if (targets.length === 0) {\n this.animationComplete.emit();\n return;\n }\n\n const dir = this.direction();\n const stepMs = this.stepDuration() * 1000;\n const totalMs = stepMs * 2;\n const stagger = this.delay();\n const easing = this.easing();\n\n // Three-keyframe shape per reactbits:\n // 0 → fully blurred, offset, transparent\n // 50% → half-blur, half-opacity, small reverse offset\n // 100% → clear, opaque, at rest\n const startY = dir === 'top' ? -50 : 50;\n const midY = dir === 'top' ? 5 : -5;\n const keyframes: Keyframe[] = [\n { opacity: 0, filter: 'blur(10px)', transform: `translate3d(0, ${startY}px, 0)` },\n { opacity: 0.5, filter: 'blur(5px)', transform: `translate3d(0, ${midY}px, 0)`, offset: 0.5 },\n { opacity: 1, filter: 'blur(0px)', transform: 'translate3d(0, 0, 0)' },\n ];\n\n let remaining = targets.length;\n targets.forEach((el, i) => {\n el.style.opacity = '0';\n el.style.filter = 'blur(10px)';\n el.style.transform = `translate3d(0, ${startY}px, 0)`;\n const animation = el.animate(keyframes, {\n duration: totalMs,\n delay: i * stagger,\n easing,\n fill: 'forwards',\n });\n animation.onfinish = (): void => {\n el.style.opacity = '1';\n el.style.filter = 'blur(0px)';\n el.style.transform = 'translate3d(0, 0, 0)';\n try {\n animation.cancel();\n } catch {\n /* noop */\n }\n remaining -= 1;\n if (remaining === 0) this.animationComplete.emit();\n };\n });\n }\n}\n\nexport type { WrBlurTextDirection, WrBlurTextUnit } from './interfaces';\n","<!-- The readable text, once: the pieces below are per-character spans. -->\n<span class=\"wr-blur-text__sr-only\">{{ text() }}</span>\n@for (p of pieces(); track $index) {\n @if (p.kind === 'piece') {\n <span class=\"wr-blur-text__piece\" aria-hidden=\"true\">{{ p.text }}</span>\n } @else {\n <span class=\"wr-blur-text__space\" aria-hidden=\"true\">{{ p.text }}</span>\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-blur-text.mjs","sources":["../../../projects/lib/blur-text/blur-text.ts","../../../projects/lib/blur-text/blur-text.html","../../../projects/lib/blur-text/ngwr-blur-text.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Angular adaptation of the BlurText effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/text-animations/blur-text\n *\n * The reactbits version uses `motion/react` (framer-motion successor).\n * This port is dependency-free — uses the Web Animations API for the\n * three-keyframe blur-and-fade-in, triggered by `IntersectionObserver`.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n output,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrBlurTextDirection, WrBlurTextUnit } from './interfaces';\n\ntype Piece = { readonly kind: 'piece'; readonly text: string } | { readonly kind: 'space'; readonly text: string };\n\nfunction splitPieces(text: string, unit: WrBlurTextUnit): readonly Piece[] {\n if (!text) return [];\n if (unit === 'words') {\n return text.split(/(\\s+)/).flatMap<Piece>(seg => {\n if (seg.length === 0) return [];\n if (/^\\s+$/.test(seg)) return [{ kind: 'space', text: seg }];\n return [{ kind: 'piece', text: seg }];\n });\n }\n return [...text].map<Piece>(ch => (/\\s/.test(ch) ? { kind: 'space', text: ch } : { kind: 'piece', text: ch }));\n}\n\n/**\n * Reveals text by splitting it into chars or words and animating each\n * piece in from a blurred / offset state through a brief mid-step into\n * the steady state. Triggers when the host enters the viewport.\n *\n * @example\n * ```html\n * <wr-blur-text\n * text=\"Welcome to ngwr\"\n * animateBy=\"words\"\n * direction=\"top\"\n * [delay]=\"120\"\n * [stepDuration]=\"0.4\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/text-animations/blur-text\n */\n@Component({\n selector: 'wr-blur-text',\n templateUrl: './blur-text.html',\n styleUrl: './blur-text.scss',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-blur-text' },\n})\nexport class WrBlurText {\n /** Text to animate. Required. */\n readonly text = input.required<string>();\n\n /** Split granularity. @default 'words' */\n readonly animateBy = input<WrBlurTextUnit>('words');\n\n /** Entry direction. `'top'` slides down into place; `'bottom'` slides up. @default 'top' */\n readonly direction = input<WrBlurTextDirection>('top');\n\n /** Per-piece stagger in ms. @default 200 */\n readonly delay = input(200, { transform: numAttr(200) });\n\n /** Duration of each keyframe step in seconds (total = 2 × stepDuration). @default 0.35 */\n readonly stepDuration = input(0.35, { transform: numAttr(0.35) });\n\n /** CSS easing function. @default 'linear' (matches reactbits' identity easing) */\n readonly easing = input('linear');\n\n /** `IntersectionObserver.threshold` (0..1). @default 0.1 */\n readonly threshold = input(0.1, { transform: numAttr(0.1) });\n\n /** `IntersectionObserver.rootMargin`. @default '0px' */\n readonly rootMargin = input('0px');\n\n /** Emitted once all pieces finish animating. */\n readonly animationComplete = output<void>();\n\n protected readonly pieces = computed(() => splitPieces(this.text(), this.animateBy()));\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly platform = inject(WrPlatform);\n\n private hasAnimated = false;\n\n /**\n * The ONE observer, replaced rather than stacked.\n *\n * `startObserver()` used to `new IntersectionObserver(…)` and register its own\n * `destroyRef.onDestroy` every time, and the effect below calls it on every\n * `text` change after setting `hasAnimated = false` — so the guard at the top\n * of `startObserver` never blocked that path. An off-screen element (the only\n * case where the observer does not immediately disconnect itself) therefore\n * leaked one live observer AND one destroy callback per change, all of them\n * watching the same host and all of them able to fire `animate()`.\n */\n private observer: IntersectionObserver | null = null;\n\n /**\n * The effect below reaches `startObserver()` through a `queueMicrotask`, so\n * the component can be destroyed between the two. The destroy hook runs first\n * and disconnects whatever exists at that moment — then the continuation\n * builds a FRESH observer on a host Angular has already detached, and nothing\n * is left to disconnect it. Same hazard, and same flag, as `wr-split-text`.\n */\n private destroyed = false;\n\n constructor() {\n if (!this.isBrowser) return;\n this.destroyRef.onDestroy(() => {\n this.destroyed = true;\n this.observer?.disconnect();\n });\n afterNextRender(() => this.startObserver());\n effect(() => {\n this.text();\n this.animateBy();\n this.hasAnimated = false;\n queueMicrotask(() => this.startObserver());\n });\n }\n\n private startObserver(): void {\n if (this.destroyed || this.hasAnimated) return;\n const host = this.host.nativeElement;\n this.observer?.disconnect();\n const io = new IntersectionObserver(\n (entries, obs) => {\n for (const e of entries) {\n if (e.isIntersecting) {\n obs.disconnect();\n this.animate();\n break;\n }\n }\n },\n { threshold: this.threshold(), rootMargin: this.rootMargin() }\n );\n this.observer = io;\n io.observe(host);\n }\n\n private animate(): void {\n if (this.hasAnimated) return;\n this.hasAnimated = true;\n\n // Reduced motion: pieces already render clear and in place — skip\n // the blur tween and report completion right away.\n if (this.platform.prefersReducedMotion()) {\n this.animationComplete.emit();\n return;\n }\n\n const targets = this.host.nativeElement.querySelectorAll<HTMLElement>('.wr-blur-text__piece');\n if (targets.length === 0) {\n this.animationComplete.emit();\n return;\n }\n\n const dir = this.direction();\n const stepMs = this.stepDuration() * 1000;\n const totalMs = stepMs * 2;\n const stagger = this.delay();\n const easing = this.easing();\n\n // Three-keyframe shape per reactbits:\n // 0 → fully blurred, offset, transparent\n // 50% → half-blur, half-opacity, small reverse offset\n // 100% → clear, opaque, at rest\n const startY = dir === 'top' ? -50 : 50;\n const midY = dir === 'top' ? 5 : -5;\n const keyframes: Keyframe[] = [\n { opacity: 0, filter: 'blur(10px)', transform: `translate3d(0, ${startY}px, 0)` },\n { opacity: 0.5, filter: 'blur(5px)', transform: `translate3d(0, ${midY}px, 0)`, offset: 0.5 },\n { opacity: 1, filter: 'blur(0px)', transform: 'translate3d(0, 0, 0)' },\n ];\n\n let remaining = targets.length;\n targets.forEach((el, i) => {\n el.style.opacity = '0';\n el.style.filter = 'blur(10px)';\n el.style.transform = `translate3d(0, ${startY}px, 0)`;\n const animation = el.animate(keyframes, {\n duration: totalMs,\n delay: i * stagger,\n easing,\n fill: 'forwards',\n });\n animation.onfinish = (): void => {\n el.style.opacity = '1';\n el.style.filter = 'blur(0px)';\n el.style.transform = 'translate3d(0, 0, 0)';\n try {\n animation.cancel();\n } catch {\n /* noop */\n }\n remaining -= 1;\n if (remaining === 0) this.animationComplete.emit();\n };\n });\n }\n}\n\nexport type { WrBlurTextDirection, WrBlurTextUnit } from './interfaces';\n","<!-- The readable text, once: the pieces below are per-character spans. -->\n<span class=\"wr-blur-text__sr-only\">{{ text() }}</span>\n@for (p of pieces(); track $index) {\n @if (p.kind === 'piece') {\n <span class=\"wr-blur-text__piece\" aria-hidden=\"true\">{{ p.text }}</span>\n } @else {\n <span class=\"wr-blur-text__space\" aria-hidden=\"true\">{{ p.text }}</span>\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { isPlatformBrowser } from '@angular/common';
|
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
4
|
import { input, inject, ElementRef, PLATFORM_ID, computed, effect, ViewEncapsulation, Component } from '@angular/core';
|
|
5
5
|
import { WrPlatform } from 'ngwr/platform';
|
|
6
|
-
import { numAttr } from 'ngwr/utils';
|
|
6
|
+
import { isSafeCssValue, numAttr } from 'ngwr/utils';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @license
|
|
@@ -48,7 +48,11 @@ function buildGlowVars(glowColor, intensity) {
|
|
|
48
48
|
}
|
|
49
49
|
function buildGradientVars(colors) {
|
|
50
50
|
const out = {};
|
|
51
|
-
|
|
51
|
+
// Each entry is INTERPOLATED into the `radial-gradient()` below, so a value
|
|
52
|
+
// that balances the parens around it escapes into a sibling layer and can add
|
|
53
|
+
// a `url()` the browser fetches. One unsafe entry discards the palette — the
|
|
54
|
+
// same all-or-nothing rule `wr-gradient-text` follows, for the same reason.
|
|
55
|
+
const palette = colors.length > 0 && colors.every(color => isSafeCssValue(color)) ? colors : DEFAULT_COLORS;
|
|
52
56
|
for (let i = 0; i < GRADIENT_VAR_NAMES.length; i++) {
|
|
53
57
|
const colour = palette[Math.min(GRADIENT_COLOR_INDEX[i], palette.length - 1)];
|
|
54
58
|
out[GRADIENT_VAR_NAMES[i]] = `radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${colour} 0px, transparent 50%)`;
|
|
@@ -150,8 +154,13 @@ class WrBorderGlow {
|
|
|
150
154
|
Object.assign(vars, buildGradientVars(colors));
|
|
151
155
|
// Colour vars are written only when set — otherwise the stylesheet's
|
|
152
156
|
// per-theme defaults (light vs dark) take over.
|
|
157
|
+
// Composed, not passed through: `_rules.scss` reads this var inside a
|
|
158
|
+
// `linear-gradient()` and again in a `background` shorthand, where a `url()`
|
|
159
|
+
// is valid and fetches. An audit bound `url("…")` here and recorded the
|
|
160
|
+
// request. Validated like its `colors` sibling on this same component —
|
|
161
|
+
// leaving one of the two unchecked is two rules inside one component.
|
|
153
162
|
const bg = this.backgroundColor();
|
|
154
|
-
if (bg !== null)
|
|
163
|
+
if (bg !== null && isSafeCssValue(bg))
|
|
155
164
|
vars['--wr-border-glow-bg'] = bg;
|
|
156
165
|
const glow = this.glowColor();
|
|
157
166
|
if (glow !== null)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-border-glow.mjs","sources":["../../../projects/lib/border-glow/border-glow.ts","../../../projects/lib/border-glow/border-glow.html","../../../projects/lib/border-glow/ngwr-border-glow.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Port of the reactbits.dev BorderGlow effect — a cursor-tracked cone of\n * mesh-gradient light that follows the pointer along the card's perimeter,\n * with an outer halo and optional auto-sweep on mount.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport { Component, ElementRef, PLATFORM_ID, ViewEncapsulation, computed, effect, inject, input } from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\nconst RADIANS_TO_DEG = 180 / Math.PI;\n\ninterface ParsedHsl {\n readonly h: number;\n readonly s: number;\n readonly l: number;\n}\n\nfunction parseHsl(input: string): ParsedHsl {\n const match = /([\\d.]+)\\s*([\\d.]+)%?\\s*([\\d.]+)%?/.exec(input);\n if (!match) return { h: 40, s: 80, l: 80 };\n return { h: Number.parseFloat(match[1]), s: Number.parseFloat(match[2]), l: Number.parseFloat(match[3]) };\n}\n\nconst GLOW_OPACITY_STEPS = [100, 60, 50, 40, 30, 20, 10] as const;\nconst GLOW_VAR_SUFFIXES = ['', '-60', '-50', '-40', '-30', '-20', '-10'] as const;\n\nconst GRADIENT_POSITIONS = ['80% 55%', '69% 34%', '8% 6%', '41% 38%', '86% 85%', '82% 18%', '51% 4%'] as const;\nconst GRADIENT_VAR_NAMES = [\n '--wr-border-glow-gradient-one',\n '--wr-border-glow-gradient-two',\n '--wr-border-glow-gradient-three',\n '--wr-border-glow-gradient-four',\n '--wr-border-glow-gradient-five',\n '--wr-border-glow-gradient-six',\n '--wr-border-glow-gradient-seven',\n] as const;\nconst GRADIENT_COLOR_INDEX = [0, 1, 2, 0, 1, 2, 1] as const;\n\nconst DEFAULT_COLORS: readonly string[] = ['#c084fc', '#f472b6', '#38bdf8'];\n\nfunction buildGlowVars(glowColor: string, intensity: number): Record<string, string> {\n const { h, s, l } = parseHsl(glowColor);\n const base = `${h}deg ${s}% ${l}%`;\n const out: Record<string, string> = {};\n for (let i = 0; i < GLOW_OPACITY_STEPS.length; i++) {\n const alpha = Math.min(GLOW_OPACITY_STEPS[i] * intensity, 100);\n out[`--wr-border-glow-color${GLOW_VAR_SUFFIXES[i]}`] = `hsl(${base} / ${alpha}%)`;\n }\n return out;\n}\n\nfunction buildGradientVars(colors: readonly string[]): Record<string, string> {\n const out: Record<string, string> = {};\n const palette = colors.length > 0 ? colors : DEFAULT_COLORS;\n for (let i = 0; i < GRADIENT_VAR_NAMES.length; i++) {\n const colour = palette[Math.min(GRADIENT_COLOR_INDEX[i], palette.length - 1)];\n out[GRADIENT_VAR_NAMES[i]] = `radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${colour} 0px, transparent 50%)`;\n }\n out['--wr-border-glow-gradient-base'] = `linear-gradient(${palette[0]} 0 100%)`;\n return out;\n}\n\ninterface AnimateOptions {\n readonly start?: number;\n readonly end?: number;\n readonly duration?: number;\n readonly delay?: number;\n readonly ease?: (t: number) => number;\n readonly onUpdate: (v: number) => void;\n readonly onEnd?: () => void;\n}\n\n/** Tiny rAF-driven tweener. Returns a cancel function so callers can clean up on destroy. */\nfunction animateValue(opts: AnimateOptions): () => void {\n const { start = 0, end = 100, duration = 1000, delay = 0, ease = (t: number): number => t, onUpdate, onEnd } = opts;\n let raf = 0;\n let cancelled = false;\n\n const startAt = performance.now() + delay;\n const tick = (): void => {\n if (cancelled) return;\n const elapsed = performance.now() - startAt;\n const t = Math.min(Math.max(elapsed / duration, 0), 1);\n onUpdate(start + (end - start) * ease(t));\n if (t < 1) raf = requestAnimationFrame(tick);\n else if (onEnd) onEnd();\n };\n\n const timer = setTimeout(() => {\n raf = requestAnimationFrame(tick);\n }, delay);\n\n return () => {\n cancelled = true;\n clearTimeout(timer);\n cancelAnimationFrame(raf);\n };\n}\n\n/**\n * Cursor-tracked glowing border card. A cone of light follows the pointer\n * along the card's perimeter, brightening as the cursor approaches an edge.\n *\n * - Outer halo via stacked `box-shadow` on an inner `.edge-light` element.\n * - Inner \"soft bloom\" via `mix-blend-mode: soft-light` for content near the\n * edges (subtle; control via `[fillOpacity]`).\n * - Optional `[animated]` mode performs a one-shot sweep on mount, useful\n * for drawing attention to a freshly-rendered card.\n *\n * @example\n * ```html\n * <wr-border-glow [animated]=\"true\" [colors]=\"['#c084fc', '#f472b6', '#38bdf8']\">\n * <h3>Hover me</h3>\n * <p>The border lights up where the cursor is.</p>\n * </wr-border-glow>\n * ```\n *\n * @see https://www.reactbits.dev/components/border-glow (visual reference)\n */\n@Component({\n selector: 'wr-border-glow',\n templateUrl: './border-glow.html',\n styleUrl: './border-glow.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-border-glow',\n '(pointermove)': 'onPointerMove($event)',\n },\n})\nexport class WrBorderGlow {\n /** Card background fill. When unset, uses the theme surface token. */\n readonly backgroundColor = input<string | null>(null);\n\n /** Corner radius in pixels. @default 28 */\n readonly borderRadius = input(28, { transform: numAttr(28) });\n\n /** Halo extent in pixels — how far the outer glow reaches past the card edge. @default 40 */\n readonly glowRadius = input(40, { transform: numAttr(40) });\n\n /** Halo opacity multiplier (1 = full strength). @default 1 */\n readonly glowIntensity = input(1, { transform: numAttr(1) });\n\n /** Width of the lit cone as a percentage of the perimeter. @default 25 */\n readonly coneSpread = input(25, { transform: numAttr(25) });\n\n /** How sharply the halo fades as the cursor leaves the edge. Lower = wider falloff. @default 30 */\n readonly edgeSensitivity = input(30, { transform: numAttr(30) });\n\n /** Strength of the soft-light interior fill near edges. @default 0.5 */\n readonly fillOpacity = input(0.5, { transform: numAttr(0.5) });\n\n /**\n * Halo colour as `'H S L'` (HSL parts, no commas). When unset, the theme\n * decides: a deep amber on light surfaces, pale amber on dark. Note\n * `glowIntensity` only applies alongside an explicit `glowColor`.\n */\n readonly glowColor = input<string | null>(null);\n\n /**\n * Mesh-gradient palette for the colored border slice. When unset, the\n * theme decides: deep tones on light, pastels on dark.\n */\n readonly colors = input<readonly string[] | null>(null);\n\n /** Play a one-shot perimeter sweep on mount, then fade out. @default false */\n readonly animated = input(false, { transform: coerceBooleanProperty });\n\n private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n /** Inline CSS variable bag — recomputed when colour / radius / sensitivity inputs change. */\n protected readonly cssVars = computed<Record<string, string>>(() => {\n const vars: Record<string, string> = {\n '--wr-border-glow-radius': `${this.borderRadius()}px`,\n '--wr-border-glow-padding': `${this.glowRadius()}px`,\n '--wr-border-glow-cone-spread': String(this.coneSpread()),\n '--wr-border-glow-edge-sensitivity': String(this.edgeSensitivity()),\n '--wr-border-glow-fill-opacity': String(this.fillOpacity()),\n };\n const colors = this.colors();\n if (colors !== null) Object.assign(vars, buildGradientVars(colors));\n // Colour vars are written only when set — otherwise the stylesheet's\n // per-theme defaults (light vs dark) take over.\n const bg = this.backgroundColor();\n if (bg !== null) vars['--wr-border-glow-bg'] = bg;\n const glow = this.glowColor();\n if (glow !== null) Object.assign(vars, buildGlowVars(glow, this.glowIntensity()));\n return vars;\n });\n\n constructor() {\n // Apply the variable bag to the host on every input change. Bind in an\n // effect rather than `host: { '[style.--foo]': '...' }` so a single\n // `setProperty` loop handles the lot.\n effect(() => {\n const host = this.el.nativeElement;\n const vars = this.cssVars();\n for (const [k, v] of Object.entries(vars)) host.style.setProperty(k, v);\n });\n\n if (!this.isBrowser) return;\n\n // Auto-sweep on mount — fires once when `animated` is true.\n effect(onCleanup => {\n if (!this.animated()) return;\n onCleanup(this.runAutoSweep());\n });\n }\n\n /** Pointer-driven update of `--wr-border-glow-angle` + `--wr-border-glow-edge-proximity`. */\n protected onPointerMove(event: PointerEvent): void {\n const host = this.el.nativeElement;\n const rect = host.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const cx = rect.width / 2;\n const cy = rect.height / 2;\n\n const dx = x - cx;\n const dy = y - cy;\n\n // `edge-proximity`: 0 at centre, 1 at the perimeter (whichever axis hits first).\n let edge = 0;\n if (dx !== 0 || dy !== 0) {\n const kx = dx === 0 ? Infinity : cx / Math.abs(dx);\n const ky = dy === 0 ? Infinity : cy / Math.abs(dy);\n edge = Math.min(Math.max(1 / Math.min(kx, ky), 0), 1);\n }\n\n // `cursor-angle`: 0deg = top, increases clockwise.\n let angle = 0;\n if (dx !== 0 || dy !== 0) {\n angle = Math.atan2(dy, dx) * RADIANS_TO_DEG + 90;\n if (angle < 0) angle += 360;\n }\n\n host.style.setProperty('--wr-border-glow-edge-proximity', (edge * 100).toFixed(3));\n host.style.setProperty('--wr-border-glow-angle', `${angle.toFixed(3)}deg`);\n }\n\n /** One-shot perimeter sweep: in → around → out, mirroring the reactbits behaviour. */\n private runAutoSweep(): () => void {\n // Reduced motion: skip the decorative mount sweep. The pointer-driven\n // glow stays — it maps 1:1 to the cursor with no autonomous motion.\n if (this.platform.prefersReducedMotion()) return () => undefined;\n const host = this.el.nativeElement;\n const ANGLE_START = 110;\n const ANGLE_END = 465;\n host.classList.add('wr-border-glow--sweeping');\n host.style.setProperty('--wr-border-glow-angle', `${ANGLE_START}deg`);\n\n const easeOutCubic = (t: number): number => 1 - (1 - t) ** 3;\n const easeInCubic = (t: number): number => t * t * t;\n const lerpAngle = (v: number): string => `${(ANGLE_END - ANGLE_START) * (v / 100) + ANGLE_START}deg`;\n\n // 1. Ramp edge-proximity up to 100 quickly.\n const cancelA = animateValue({\n duration: 500,\n onUpdate: v => host.style.setProperty('--wr-border-glow-edge-proximity', `${v}`),\n });\n // 2. Sweep angle first half (ease-in).\n const cancelB = animateValue({\n duration: 1500,\n end: 50,\n ease: easeInCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-angle', lerpAngle(v)),\n });\n // 3. Sweep angle second half (ease-out).\n const cancelC = animateValue({\n delay: 1500,\n duration: 2250,\n start: 50,\n end: 100,\n ease: easeOutCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-angle', lerpAngle(v)),\n });\n // 4. Fade proximity back to 0.\n const cancelD = animateValue({\n delay: 2500,\n duration: 1500,\n start: 100,\n end: 0,\n ease: easeInCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-edge-proximity', `${v}`),\n onEnd: () => host.classList.remove('wr-border-glow--sweeping'),\n });\n\n // Returned rather than registered on `destroyRef`: this method runs from an\n // effect, so registering there stacked one more teardown per re-run and none\n // of them fired until the component died. The caller ties it to the effect's\n // own cleanup instead.\n return () => {\n cancelA();\n cancelB();\n cancelC();\n cancelD();\n host.classList.remove('wr-border-glow--sweeping');\n };\n }\n}\n","<span class=\"wr-border-glow__edge\" aria-hidden=\"true\"></span>\n<div class=\"wr-border-glow__inner\">\n <ng-content />\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;AAAA;;;;;;;;;AASG;AASH,MAAM,cAAc,GAAG,GAAG,GAAG,IAAI,CAAC,EAAE;AAQpC,SAAS,QAAQ,CAAC,KAAa,EAAA;IAC7B,MAAM,KAAK,GAAG,oCAAoC,CAAC,IAAI,CAAC,KAAK,CAAC;AAC9D,IAAA,IAAI,CAAC,KAAK;AAAE,QAAA,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;AAC1C,IAAA,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE;AAC3G;AAEA,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAU;AACjE,MAAM,iBAAiB,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAU;AAEjF,MAAM,kBAAkB,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAU;AAC9G,MAAM,kBAAkB,GAAG;IACzB,+BAA+B;IAC/B,+BAA+B;IAC/B,iCAAiC;IACjC,gCAAgC;IAChC,gCAAgC;IAChC,+BAA+B;IAC/B,iCAAiC;CACzB;AACV,MAAM,oBAAoB,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAU;AAE3D,MAAM,cAAc,GAAsB,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC;AAE3E,SAAS,aAAa,CAAC,SAAiB,EAAE,SAAiB,EAAA;AACzD,IAAA,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,QAAQ,CAAC,SAAS,CAAC;IACvC,MAAM,IAAI,GAAG,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA,EAAA,EAAK,CAAC,CAAA,CAAA,CAAG;IAClC,MAAM,GAAG,GAA2B,EAAE;AACtC,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,kBAAkB,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AAClD,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,GAAG,SAAS,EAAE,GAAG,CAAC;AAC9D,QAAA,GAAG,CAAC,CAAA,sBAAA,EAAyB,iBAAiB,CAAC,CAAC,CAAC,CAAA,CAAE,CAAC,GAAG,CAAA,IAAA,EAAO,IAAI,CAAA,GAAA,EAAM,KAAK,IAAI;IACnF;AACA,IAAA,OAAO,GAAG;AACZ;AAEA,SAAS,iBAAiB,CAAC,MAAyB,EAAA;IAClD,MAAM,GAAG,GAA2B,EAAE;AACtC,IAAA,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,GAAG,cAAc;AAC3D,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,kBAAkB,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QAClD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC7E,QAAA,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA,mBAAA,EAAsB,kBAAkB,CAAC,CAAC,CAAC,CAAA,EAAA,EAAK,MAAM,wBAAwB;IAC7G;IACA,GAAG,CAAC,gCAAgC,CAAC,GAAG,CAAA,gBAAA,EAAmB,OAAO,CAAC,CAAC,CAAC,CAAA,QAAA,CAAU;AAC/E,IAAA,OAAO,GAAG;AACZ;AAYA;AACA,SAAS,YAAY,CAAC,IAAoB,EAAA;AACxC,IAAA,MAAM,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,GAAG,GAAG,EAAE,QAAQ,GAAG,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,CAAS,KAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,IAAI;IACnH,IAAI,GAAG,GAAG,CAAC;IACX,IAAI,SAAS,GAAG,KAAK;IAErB,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK;IACzC,MAAM,IAAI,GAAG,MAAW;AACtB,QAAA,IAAI,SAAS;YAAE;QACf,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO;AAC3C,QAAA,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;AACtD,QAAA,QAAQ,CAAC,KAAK,GAAG,CAAC,GAAG,GAAG,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;QACzC,IAAI,CAAC,GAAG,CAAC;AAAE,YAAA,GAAG,GAAG,qBAAqB,CAAC,IAAI,CAAC;AACvC,aAAA,IAAI,KAAK;AAAE,YAAA,KAAK,EAAE;AACzB,IAAA,CAAC;AAED,IAAA,MAAM,KAAK,GAAG,UAAU,CAAC,MAAK;AAC5B,QAAA,GAAG,GAAG,qBAAqB,CAAC,IAAI,CAAC;IACnC,CAAC,EAAE,KAAK,CAAC;AAET,IAAA,OAAO,MAAK;QACV,SAAS,GAAG,IAAI;QAChB,YAAY,CAAC,KAAK,CAAC;QACnB,oBAAoB,CAAC,GAAG,CAAC;AAC3B,IAAA,CAAC;AACH;AAEA;;;;;;;;;;;;;;;;;;;AAmBG;MAWU,YAAY,CAAA;;IAEd,eAAe,GAAG,KAAK,CAAgB,IAAI;wFAAC;;AAG5C,IAAA,YAAY,GAAG,KAAK,CAAC,EAAE,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,cAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,EAAA,CAAG;;AAGpD,IAAA,UAAU,GAAG,KAAK,CAAC,EAAE,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,YAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,EAAA,CAAG;;AAGlD,IAAA,aAAa,GAAG,KAAK,CAAC,CAAC,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,eAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,EAAA,CAAG;;AAGnD,IAAA,UAAU,GAAG,KAAK,CAAC,EAAE,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,YAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,EAAA,CAAG;;AAGlD,IAAA,eAAe,GAAG,KAAK,CAAC,EAAE,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,iBAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,EAAA,CAAG;;AAGvD,IAAA,WAAW,GAAG,KAAK,CAAC,GAAG,EAAA,EAAA,IAAA,SAAA,GAAA,EAAA,SAAA,EAAA,aAAA,EAAA,8BAAA,EAAA,CAAA,EAAI,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,EAAA,CAAG;AAE9D;;;;AAIG;IACM,SAAS,GAAG,KAAK,CAAgB,IAAI;kFAAC;AAE/C;;;AAGG;IACM,MAAM,GAAG,KAAK,CAA2B,IAAI;+EAAC;;IAG9C,QAAQ,GAAG,KAAK,CAAC,KAAK,gFAAI,SAAS,EAAE,qBAAqB,EAAA,CAAG;AAErD,IAAA,EAAE,GAAG,MAAM,CAA0B,UAAU,CAAC;AAChD,IAAA,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC;IAC7B,SAAS,GAAG,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;;AAGhD,IAAA,OAAO,GAAG,QAAQ,CAAyB,MAAK;AACjE,QAAA,MAAM,IAAI,GAA2B;AACnC,YAAA,yBAAyB,EAAE,CAAA,EAAG,IAAI,CAAC,YAAY,EAAE,CAAA,EAAA,CAAI;AACrD,YAAA,0BAA0B,EAAE,CAAA,EAAG,IAAI,CAAC,UAAU,EAAE,CAAA,EAAA,CAAI;AACpD,YAAA,8BAA8B,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;AACzD,YAAA,mCAAmC,EAAE,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;AACnE,YAAA,+BAA+B,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;SAC5D;AACD,QAAA,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE;QAC5B,IAAI,MAAM,KAAK,IAAI;YAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC;;;AAGnE,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,eAAe,EAAE;QACjC,IAAI,EAAE,KAAK,IAAI;AAAE,YAAA,IAAI,CAAC,qBAAqB,CAAC,GAAG,EAAE;AACjD,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE;QAC7B,IAAI,IAAI,KAAK,IAAI;AAAE,YAAA,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;AACjF,QAAA,OAAO,IAAI;IACb,CAAC;gFAAC;AAEF,IAAA,WAAA,GAAA;;;;QAIE,MAAM,CAAC,MAAK;AACV,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa;AAClC,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE;AAC3B,YAAA,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;gBAAE,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;AACzE,QAAA,CAAC,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE;;QAGrB,MAAM,CAAC,SAAS,IAAG;AACjB,YAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;gBAAE;AACtB,YAAA,SAAS,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;AAChC,QAAA,CAAC,CAAC;IACJ;;AAGU,IAAA,aAAa,CAAC,KAAmB,EAAA;AACzC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa;AAClC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,qBAAqB,EAAE;QACzC,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI;QACnC,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG;AAClC,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC;AACzB,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC;AAE1B,QAAA,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE;AACjB,QAAA,MAAM,EAAE,GAAG,CAAC,GAAG,EAAE;;QAGjB,IAAI,IAAI,GAAG,CAAC;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE;YACxB,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,QAAQ,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAClD,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,QAAQ,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAClD,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QACvD;;QAGA,IAAI,KAAK,GAAG,CAAC;QACb,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE;AACxB,YAAA,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,cAAc,GAAG,EAAE;YAChD,IAAI,KAAK,GAAG,CAAC;gBAAE,KAAK,IAAI,GAAG;QAC7B;AAEA,QAAA,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,iCAAiC,EAAE,CAAC,IAAI,GAAG,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAClF,QAAA,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,wBAAwB,EAAE,CAAA,EAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA,GAAA,CAAK,CAAC;IAC5E;;IAGQ,YAAY,GAAA;;;AAGlB,QAAA,IAAI,IAAI,CAAC,QAAQ,CAAC,oBAAoB,EAAE;AAAE,YAAA,OAAO,MAAM,SAAS;AAChE,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa;QAClC,MAAM,WAAW,GAAG,GAAG;QACvB,MAAM,SAAS,GAAG,GAAG;AACrB,QAAA,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,0BAA0B,CAAC;QAC9C,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,wBAAwB,EAAE,CAAA,EAAG,WAAW,CAAA,GAAA,CAAK,CAAC;AAErE,QAAA,MAAM,YAAY,GAAG,CAAC,CAAS,KAAa,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;AAC5D,QAAA,MAAM,WAAW,GAAG,CAAC,CAAS,KAAa,CAAC,GAAG,CAAC,GAAG,CAAC;QACpD,MAAM,SAAS,GAAG,CAAC,CAAS,KAAa,CAAA,EAAG,CAAC,SAAS,GAAG,WAAW,KAAK,CAAC,GAAG,GAAG,CAAC,GAAG,WAAW,CAAA,GAAA,CAAK;;QAGpG,MAAM,OAAO,GAAG,YAAY,CAAC;AAC3B,YAAA,QAAQ,EAAE,GAAG;AACb,YAAA,QAAQ,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,iCAAiC,EAAE,CAAA,EAAG,CAAC,EAAE,CAAC;AACjF,SAAA,CAAC;;QAEF,MAAM,OAAO,GAAG,YAAY,CAAC;AAC3B,YAAA,QAAQ,EAAE,IAAI;AACd,YAAA,GAAG,EAAE,EAAE;AACP,YAAA,IAAI,EAAE,WAAW;AACjB,YAAA,QAAQ,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;AAC9E,SAAA,CAAC;;QAEF,MAAM,OAAO,GAAG,YAAY,CAAC;AAC3B,YAAA,KAAK,EAAE,IAAI;AACX,YAAA,QAAQ,EAAE,IAAI;AACd,YAAA,KAAK,EAAE,EAAE;AACT,YAAA,GAAG,EAAE,GAAG;AACR,YAAA,IAAI,EAAE,YAAY;AAClB,YAAA,QAAQ,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;AAC9E,SAAA,CAAC;;QAEF,MAAM,OAAO,GAAG,YAAY,CAAC;AAC3B,YAAA,KAAK,EAAE,IAAI;AACX,YAAA,QAAQ,EAAE,IAAI;AACd,YAAA,KAAK,EAAE,GAAG;AACV,YAAA,GAAG,EAAE,CAAC;AACN,YAAA,IAAI,EAAE,WAAW;AACjB,YAAA,QAAQ,EAAE,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,iCAAiC,EAAE,CAAA,EAAG,CAAC,EAAE,CAAC;YAChF,KAAK,EAAE,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,0BAA0B,CAAC;AAC/D,SAAA,CAAC;;;;;AAMF,QAAA,OAAO,MAAK;AACV,YAAA,OAAO,EAAE;AACT,YAAA,OAAO,EAAE;AACT,YAAA,OAAO,EAAE;AACT,YAAA,OAAO,EAAE;AACT,YAAA,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,0BAA0B,CAAC;AACnD,QAAA,CAAC;IACH;uGA1KW,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAZ,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,YAAY,0hDC1IzB,sIAIA,EAAA,MAAA,EAAA,CAAA,i9SAAA,CAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FDsIa,YAAY,EAAA,UAAA,EAAA,CAAA;kBAVxB,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,gBAAgB,EAAA,aAAA,EAGX,iBAAiB,CAAC,IAAI,EAAA,IAAA,EAC/B;AACJ,wBAAA,KAAK,EAAE,gBAAgB;AACvB,wBAAA,eAAe,EAAE,uBAAuB;AACzC,qBAAA,EAAA,QAAA,EAAA,sIAAA,EAAA,MAAA,EAAA,CAAA,i9SAAA,CAAA,EAAA;;;AExIH;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"ngwr-border-glow.mjs","sources":["../../../projects/lib/border-glow/border-glow.ts","../../../projects/lib/border-glow/border-glow.html","../../../projects/lib/border-glow/ngwr-border-glow.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Port of the reactbits.dev BorderGlow effect — a cursor-tracked cone of\n * mesh-gradient light that follows the pointer along the card's perimeter,\n * with an outer halo and optional auto-sweep on mount.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport { Component, ElementRef, PLATFORM_ID, ViewEncapsulation, computed, effect, inject, input } from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { isSafeCssValue, numAttr } from 'ngwr/utils';\n\nconst RADIANS_TO_DEG = 180 / Math.PI;\n\ninterface ParsedHsl {\n readonly h: number;\n readonly s: number;\n readonly l: number;\n}\n\nfunction parseHsl(input: string): ParsedHsl {\n const match = /([\\d.]+)\\s*([\\d.]+)%?\\s*([\\d.]+)%?/.exec(input);\n if (!match) return { h: 40, s: 80, l: 80 };\n return { h: Number.parseFloat(match[1]), s: Number.parseFloat(match[2]), l: Number.parseFloat(match[3]) };\n}\n\nconst GLOW_OPACITY_STEPS = [100, 60, 50, 40, 30, 20, 10] as const;\nconst GLOW_VAR_SUFFIXES = ['', '-60', '-50', '-40', '-30', '-20', '-10'] as const;\n\nconst GRADIENT_POSITIONS = ['80% 55%', '69% 34%', '8% 6%', '41% 38%', '86% 85%', '82% 18%', '51% 4%'] as const;\nconst GRADIENT_VAR_NAMES = [\n '--wr-border-glow-gradient-one',\n '--wr-border-glow-gradient-two',\n '--wr-border-glow-gradient-three',\n '--wr-border-glow-gradient-four',\n '--wr-border-glow-gradient-five',\n '--wr-border-glow-gradient-six',\n '--wr-border-glow-gradient-seven',\n] as const;\nconst GRADIENT_COLOR_INDEX = [0, 1, 2, 0, 1, 2, 1] as const;\n\nconst DEFAULT_COLORS: readonly string[] = ['#c084fc', '#f472b6', '#38bdf8'];\n\nfunction buildGlowVars(glowColor: string, intensity: number): Record<string, string> {\n const { h, s, l } = parseHsl(glowColor);\n const base = `${h}deg ${s}% ${l}%`;\n const out: Record<string, string> = {};\n for (let i = 0; i < GLOW_OPACITY_STEPS.length; i++) {\n const alpha = Math.min(GLOW_OPACITY_STEPS[i] * intensity, 100);\n out[`--wr-border-glow-color${GLOW_VAR_SUFFIXES[i]}`] = `hsl(${base} / ${alpha}%)`;\n }\n return out;\n}\n\nfunction buildGradientVars(colors: readonly string[]): Record<string, string> {\n const out: Record<string, string> = {};\n // Each entry is INTERPOLATED into the `radial-gradient()` below, so a value\n // that balances the parens around it escapes into a sibling layer and can add\n // a `url()` the browser fetches. One unsafe entry discards the palette — the\n // same all-or-nothing rule `wr-gradient-text` follows, for the same reason.\n const palette = colors.length > 0 && colors.every(color => isSafeCssValue(color)) ? colors : DEFAULT_COLORS;\n for (let i = 0; i < GRADIENT_VAR_NAMES.length; i++) {\n const colour = palette[Math.min(GRADIENT_COLOR_INDEX[i], palette.length - 1)];\n out[GRADIENT_VAR_NAMES[i]] = `radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${colour} 0px, transparent 50%)`;\n }\n out['--wr-border-glow-gradient-base'] = `linear-gradient(${palette[0]} 0 100%)`;\n return out;\n}\n\ninterface AnimateOptions {\n readonly start?: number;\n readonly end?: number;\n readonly duration?: number;\n readonly delay?: number;\n readonly ease?: (t: number) => number;\n readonly onUpdate: (v: number) => void;\n readonly onEnd?: () => void;\n}\n\n/** Tiny rAF-driven tweener. Returns a cancel function so callers can clean up on destroy. */\nfunction animateValue(opts: AnimateOptions): () => void {\n const { start = 0, end = 100, duration = 1000, delay = 0, ease = (t: number): number => t, onUpdate, onEnd } = opts;\n let raf = 0;\n let cancelled = false;\n\n const startAt = performance.now() + delay;\n const tick = (): void => {\n if (cancelled) return;\n const elapsed = performance.now() - startAt;\n const t = Math.min(Math.max(elapsed / duration, 0), 1);\n onUpdate(start + (end - start) * ease(t));\n if (t < 1) raf = requestAnimationFrame(tick);\n else if (onEnd) onEnd();\n };\n\n const timer = setTimeout(() => {\n raf = requestAnimationFrame(tick);\n }, delay);\n\n return () => {\n cancelled = true;\n clearTimeout(timer);\n cancelAnimationFrame(raf);\n };\n}\n\n/**\n * Cursor-tracked glowing border card. A cone of light follows the pointer\n * along the card's perimeter, brightening as the cursor approaches an edge.\n *\n * - Outer halo via stacked `box-shadow` on an inner `.edge-light` element.\n * - Inner \"soft bloom\" via `mix-blend-mode: soft-light` for content near the\n * edges (subtle; control via `[fillOpacity]`).\n * - Optional `[animated]` mode performs a one-shot sweep on mount, useful\n * for drawing attention to a freshly-rendered card.\n *\n * @example\n * ```html\n * <wr-border-glow [animated]=\"true\" [colors]=\"['#c084fc', '#f472b6', '#38bdf8']\">\n * <h3>Hover me</h3>\n * <p>The border lights up where the cursor is.</p>\n * </wr-border-glow>\n * ```\n *\n * @see https://www.reactbits.dev/components/border-glow (visual reference)\n */\n@Component({\n selector: 'wr-border-glow',\n templateUrl: './border-glow.html',\n styleUrl: './border-glow.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-border-glow',\n '(pointermove)': 'onPointerMove($event)',\n },\n})\nexport class WrBorderGlow {\n /** Card background fill. When unset, uses the theme surface token. */\n readonly backgroundColor = input<string | null>(null);\n\n /** Corner radius in pixels. @default 28 */\n readonly borderRadius = input(28, { transform: numAttr(28) });\n\n /** Halo extent in pixels — how far the outer glow reaches past the card edge. @default 40 */\n readonly glowRadius = input(40, { transform: numAttr(40) });\n\n /** Halo opacity multiplier (1 = full strength). @default 1 */\n readonly glowIntensity = input(1, { transform: numAttr(1) });\n\n /** Width of the lit cone as a percentage of the perimeter. @default 25 */\n readonly coneSpread = input(25, { transform: numAttr(25) });\n\n /** How sharply the halo fades as the cursor leaves the edge. Lower = wider falloff. @default 30 */\n readonly edgeSensitivity = input(30, { transform: numAttr(30) });\n\n /** Strength of the soft-light interior fill near edges. @default 0.5 */\n readonly fillOpacity = input(0.5, { transform: numAttr(0.5) });\n\n /**\n * Halo colour as `'H S L'` (HSL parts, no commas). When unset, the theme\n * decides: a deep amber on light surfaces, pale amber on dark. Note\n * `glowIntensity` only applies alongside an explicit `glowColor`.\n */\n readonly glowColor = input<string | null>(null);\n\n /**\n * Mesh-gradient palette for the colored border slice. When unset, the\n * theme decides: deep tones on light, pastels on dark.\n */\n readonly colors = input<readonly string[] | null>(null);\n\n /** Play a one-shot perimeter sweep on mount, then fade out. @default false */\n readonly animated = input(false, { transform: coerceBooleanProperty });\n\n private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n /** Inline CSS variable bag — recomputed when colour / radius / sensitivity inputs change. */\n protected readonly cssVars = computed<Record<string, string>>(() => {\n const vars: Record<string, string> = {\n '--wr-border-glow-radius': `${this.borderRadius()}px`,\n '--wr-border-glow-padding': `${this.glowRadius()}px`,\n '--wr-border-glow-cone-spread': String(this.coneSpread()),\n '--wr-border-glow-edge-sensitivity': String(this.edgeSensitivity()),\n '--wr-border-glow-fill-opacity': String(this.fillOpacity()),\n };\n const colors = this.colors();\n if (colors !== null) Object.assign(vars, buildGradientVars(colors));\n // Colour vars are written only when set — otherwise the stylesheet's\n // per-theme defaults (light vs dark) take over.\n // Composed, not passed through: `_rules.scss` reads this var inside a\n // `linear-gradient()` and again in a `background` shorthand, where a `url()`\n // is valid and fetches. An audit bound `url(\"…\")` here and recorded the\n // request. Validated like its `colors` sibling on this same component —\n // leaving one of the two unchecked is two rules inside one component.\n const bg = this.backgroundColor();\n if (bg !== null && isSafeCssValue(bg)) vars['--wr-border-glow-bg'] = bg;\n const glow = this.glowColor();\n if (glow !== null) Object.assign(vars, buildGlowVars(glow, this.glowIntensity()));\n return vars;\n });\n\n constructor() {\n // Apply the variable bag to the host on every input change. Bind in an\n // effect rather than `host: { '[style.--foo]': '...' }` so a single\n // `setProperty` loop handles the lot.\n effect(() => {\n const host = this.el.nativeElement;\n const vars = this.cssVars();\n for (const [k, v] of Object.entries(vars)) host.style.setProperty(k, v);\n });\n\n if (!this.isBrowser) return;\n\n // Auto-sweep on mount — fires once when `animated` is true.\n effect(onCleanup => {\n if (!this.animated()) return;\n onCleanup(this.runAutoSweep());\n });\n }\n\n /** Pointer-driven update of `--wr-border-glow-angle` + `--wr-border-glow-edge-proximity`. */\n protected onPointerMove(event: PointerEvent): void {\n const host = this.el.nativeElement;\n const rect = host.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n const cx = rect.width / 2;\n const cy = rect.height / 2;\n\n const dx = x - cx;\n const dy = y - cy;\n\n // `edge-proximity`: 0 at centre, 1 at the perimeter (whichever axis hits first).\n let edge = 0;\n if (dx !== 0 || dy !== 0) {\n const kx = dx === 0 ? Infinity : cx / Math.abs(dx);\n const ky = dy === 0 ? Infinity : cy / Math.abs(dy);\n edge = Math.min(Math.max(1 / Math.min(kx, ky), 0), 1);\n }\n\n // `cursor-angle`: 0deg = top, increases clockwise.\n let angle = 0;\n if (dx !== 0 || dy !== 0) {\n angle = Math.atan2(dy, dx) * RADIANS_TO_DEG + 90;\n if (angle < 0) angle += 360;\n }\n\n host.style.setProperty('--wr-border-glow-edge-proximity', (edge * 100).toFixed(3));\n host.style.setProperty('--wr-border-glow-angle', `${angle.toFixed(3)}deg`);\n }\n\n /** One-shot perimeter sweep: in → around → out, mirroring the reactbits behaviour. */\n private runAutoSweep(): () => void {\n // Reduced motion: skip the decorative mount sweep. The pointer-driven\n // glow stays — it maps 1:1 to the cursor with no autonomous motion.\n if (this.platform.prefersReducedMotion()) return () => undefined;\n const host = this.el.nativeElement;\n const ANGLE_START = 110;\n const ANGLE_END = 465;\n host.classList.add('wr-border-glow--sweeping');\n host.style.setProperty('--wr-border-glow-angle', `${ANGLE_START}deg`);\n\n const easeOutCubic = (t: number): number => 1 - (1 - t) ** 3;\n const easeInCubic = (t: number): number => t * t * t;\n const lerpAngle = (v: number): string => `${(ANGLE_END - ANGLE_START) * (v / 100) + ANGLE_START}deg`;\n\n // 1. Ramp edge-proximity up to 100 quickly.\n const cancelA = animateValue({\n duration: 500,\n onUpdate: v => host.style.setProperty('--wr-border-glow-edge-proximity', `${v}`),\n });\n // 2. Sweep angle first half (ease-in).\n const cancelB = animateValue({\n duration: 1500,\n end: 50,\n ease: easeInCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-angle', lerpAngle(v)),\n });\n // 3. Sweep angle second half (ease-out).\n const cancelC = animateValue({\n delay: 1500,\n duration: 2250,\n start: 50,\n end: 100,\n ease: easeOutCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-angle', lerpAngle(v)),\n });\n // 4. Fade proximity back to 0.\n const cancelD = animateValue({\n delay: 2500,\n duration: 1500,\n start: 100,\n end: 0,\n ease: easeInCubic,\n onUpdate: v => host.style.setProperty('--wr-border-glow-edge-proximity', `${v}`),\n onEnd: () => host.classList.remove('wr-border-glow--sweeping'),\n });\n\n // Returned rather than registered on `destroyRef`: this method runs from an\n // effect, so registering there stacked one more teardown per re-run and none\n // of them fired until the component died. The caller ties it to the effect's\n // own cleanup instead.\n return () => {\n cancelA();\n cancelB();\n cancelC();\n cancelD();\n host.classList.remove('wr-border-glow--sweeping');\n };\n }\n}\n","<span class=\"wr-border-glow__edge\" aria-hidden=\"true\"></span>\n<div class=\"wr-border-glow__inner\">\n <ng-content />\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|