ngwr 15.3.0 → 15.5.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/bits/border-glow/styles/_rules.scss +60 -49
- package/bits/click-spark/click-spark.scss +5 -1
- package/bits/click-spark/styles/_index.scss +10 -15
- package/bits/click-spark/styles/_rules.scss +20 -0
- package/comment/styles/_index.scss +270 -0
- package/dialog/styles/_index.scss +6 -0
- package/fesm2022/ngwr-bits-aurora.mjs +108 -52
- package/fesm2022/ngwr-bits-aurora.mjs.map +1 -1
- package/fesm2022/ngwr-bits-border-glow-testing.mjs +8 -8
- package/fesm2022/ngwr-bits-border-glow-testing.mjs.map +1 -1
- package/fesm2022/ngwr-bits-border-glow.mjs +29 -26
- package/fesm2022/ngwr-bits-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-bits-dot-grid.mjs +11 -14
- package/fesm2022/ngwr-bits-dot-grid.mjs.map +1 -1
- package/fesm2022/ngwr-bits-fuzzy-text.mjs +18 -4
- package/fesm2022/ngwr-bits-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-bits-ripple-grid.mjs +14 -13
- package/fesm2022/ngwr-bits-ripple-grid.mjs.map +1 -1
- package/fesm2022/ngwr-bits-splash-cursor.mjs +21 -10
- package/fesm2022/ngwr-bits-splash-cursor.mjs.map +1 -1
- package/fesm2022/ngwr-bits-waves.mjs +10 -15
- package/fesm2022/ngwr-bits-waves.mjs.map +1 -1
- package/fesm2022/ngwr-comment-testing.mjs +60 -0
- package/fesm2022/ngwr-comment-testing.mjs.map +1 -0
- package/fesm2022/ngwr-comment.mjs +516 -0
- package/fesm2022/ngwr-comment.mjs.map +1 -0
- package/fesm2022/ngwr-dialog-testing.mjs +14 -0
- package/fesm2022/ngwr-dialog-testing.mjs.map +1 -1
- package/fesm2022/ngwr-dialog.mjs +293 -134
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +2 -0
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-editor.mjs +1 -1
- package/fesm2022/ngwr-hotkey.mjs +86 -6
- package/fesm2022/ngwr-hotkey.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ar.mjs +19 -0
- package/fesm2022/ngwr-i18n-ar.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-cs.mjs +19 -0
- package/fesm2022/ngwr-i18n-cs.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-de.mjs +19 -0
- package/fesm2022/ngwr-i18n-de.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +19 -0
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-es.mjs +19 -0
- package/fesm2022/ngwr-i18n-es.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-fr.mjs +19 -0
- package/fesm2022/ngwr-i18n-fr.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-he.mjs +19 -0
- package/fesm2022/ngwr-i18n-he.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-hi.mjs +19 -0
- package/fesm2022/ngwr-i18n-hi.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-id.mjs +19 -0
- package/fesm2022/ngwr-i18n-id.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-it.mjs +19 -0
- package/fesm2022/ngwr-i18n-it.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ja.mjs +19 -0
- package/fesm2022/ngwr-i18n-ja.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ko.mjs +19 -0
- package/fesm2022/ngwr-i18n-ko.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-nl.mjs +19 -0
- package/fesm2022/ngwr-i18n-nl.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-pl.mjs +19 -0
- package/fesm2022/ngwr-i18n-pl.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-pt.mjs +19 -0
- package/fesm2022/ngwr-i18n-pt.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +19 -0
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-sv.mjs +19 -0
- package/fesm2022/ngwr-i18n-sv.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-tr.mjs +19 -0
- package/fesm2022/ngwr-i18n-tr.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-uk.mjs +19 -0
- package/fesm2022/ngwr-i18n-uk.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-vi.mjs +19 -0
- package/fesm2022/ngwr-i18n-vi.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-zh-tw.mjs +19 -0
- package/fesm2022/ngwr-i18n-zh-tw.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-zh.mjs +19 -0
- package/fesm2022/ngwr-i18n-zh.mjs.map +1 -1
- package/fesm2022/ngwr-input-number-testing.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +109 -12
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-keyboard.mjs +78 -18
- package/fesm2022/ngwr-keyboard.mjs.map +1 -1
- package/fesm2022/ngwr-pipes.mjs +162 -51
- package/fesm2022/ngwr-pipes.mjs.map +1 -1
- package/fesm2022/ngwr-platform.mjs +134 -2
- package/fesm2022/ngwr-platform.mjs.map +1 -1
- package/fesm2022/ngwr-schema-form.mjs +2 -1
- package/fesm2022/ngwr-schema-form.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +11 -16
- package/fesm2022/ngwr-statistic.mjs.map +1 -1
- package/fesm2022/ngwr-storage.mjs +54 -33
- package/fesm2022/ngwr-storage.mjs.map +1 -1
- package/fesm2022/ngwr-table.mjs +90 -34
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-transfer.mjs +1 -1
- package/fesm2022/ngwr-utils-router.mjs +165 -0
- package/fesm2022/ngwr-utils-router.mjs.map +1 -0
- package/fesm2022/ngwr-utils.mjs +311 -3
- package/fesm2022/ngwr-utils.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/i18n/ar.json +19 -0
- package/i18n/cs.json +19 -0
- package/i18n/de.json +19 -0
- package/i18n/en.json +19 -0
- package/i18n/es.json +19 -0
- package/i18n/fr.json +19 -0
- package/i18n/he.json +19 -0
- package/i18n/hi.json +19 -0
- package/i18n/id.json +19 -0
- package/i18n/it.json +19 -0
- package/i18n/ja.json +19 -0
- package/i18n/ko.json +19 -0
- package/i18n/nl.json +19 -0
- package/i18n/pl.json +19 -0
- package/i18n/pt.json +19 -0
- package/i18n/ru.json +19 -0
- package/i18n/sv.json +19 -0
- package/i18n/tr.json +19 -0
- package/i18n/uk.json +19 -0
- package/i18n/vi.json +19 -0
- package/i18n/zh-TW.json +19 -0
- package/i18n/zh.json +19 -0
- package/llms-full.txt +32 -16
- package/package.json +14 -1
- package/schematics/use/symbol-map.json +3 -0
- package/skills/ngwr/SKILL.md +4 -2
- package/skills/ngwr/references/catalog.md +3 -0
- package/table/styles/_index.scss +17 -0
- package/types/ngwr-badge.d.ts +2 -2
- package/types/ngwr-bits-aurora.d.ts +24 -2
- package/types/ngwr-bits-border-glow-testing.d.ts +9 -5
- package/types/ngwr-bits-border-glow.d.ts +10 -4
- package/types/ngwr-bits-fuzzy-text.d.ts +2 -1
- package/types/ngwr-bits-ripple-grid.d.ts +7 -2
- package/types/ngwr-bits-splash-cursor.d.ts +2 -1
- package/types/ngwr-button.d.ts +1 -1
- package/types/ngwr-comment-testing.d.ts +90 -0
- package/types/ngwr-comment.d.ts +304 -0
- package/types/ngwr-dialog-testing.d.ts +7 -0
- package/types/ngwr-dialog.d.ts +109 -6
- package/types/ngwr-divider.d.ts +1 -1
- package/types/ngwr-drawer.d.ts +1 -1
- package/types/ngwr-hotkey.d.ts +60 -3
- package/types/ngwr-image-cropper.d.ts +1 -1
- package/types/ngwr-input-number-testing.d.ts +5 -0
- package/types/ngwr-input-number.d.ts +91 -12
- package/types/ngwr-keyboard.d.ts +28 -1
- package/types/ngwr-pipes.d.ts +142 -32
- package/types/ngwr-platform.d.ts +73 -7
- package/types/ngwr-popconfirm.d.ts +2 -2
- package/types/ngwr-progress.d.ts +1 -1
- package/types/ngwr-select.d.ts +19 -13
- package/types/ngwr-skeleton.d.ts +1 -1
- package/types/ngwr-storage.d.ts +33 -3
- package/types/ngwr-table.d.ts +145 -44
- package/types/ngwr-utils-router.d.ts +95 -0
- package/types/ngwr-utils.d.ts +262 -2
- package/types/ngwr-version.d.ts +1 -1
|
@@ -47,13 +47,13 @@ const GRADIENT_POSITIONS = [
|
|
|
47
47
|
"51% 4%"
|
|
48
48
|
];
|
|
49
49
|
const GRADIENT_VAR_NAMES = [
|
|
50
|
-
"--wr-border-glow-gradient-one",
|
|
51
|
-
"--wr-border-glow-gradient-two",
|
|
52
|
-
"--wr-border-glow-gradient-three",
|
|
53
|
-
"--wr-border-glow-gradient-four",
|
|
54
|
-
"--wr-border-glow-gradient-five",
|
|
55
|
-
"--wr-border-glow-gradient-six",
|
|
56
|
-
"--wr-border-glow-gradient-seven"
|
|
50
|
+
"--wr-border-glow-input-gradient-one",
|
|
51
|
+
"--wr-border-glow-input-gradient-two",
|
|
52
|
+
"--wr-border-glow-input-gradient-three",
|
|
53
|
+
"--wr-border-glow-input-gradient-four",
|
|
54
|
+
"--wr-border-glow-input-gradient-five",
|
|
55
|
+
"--wr-border-glow-input-gradient-six",
|
|
56
|
+
"--wr-border-glow-input-gradient-seven"
|
|
57
57
|
];
|
|
58
58
|
const GRADIENT_COLOR_INDEX = [
|
|
59
59
|
0,
|
|
@@ -76,7 +76,7 @@ function buildGlowVars(glowColor, intensity) {
|
|
|
76
76
|
const out = {};
|
|
77
77
|
for (let i = 0; i < GLOW_OPACITY_STEPS.length; i++) {
|
|
78
78
|
const alpha = Math.min(GLOW_OPACITY_STEPS[i] * intensity, 100);
|
|
79
|
-
out[`--wr-border-glow-color${GLOW_VAR_SUFFIXES[i]}`] = parts ? `hsl(${base} / ${alpha}%)` : `color-mix(in srgb, ${base} ${alpha}%, transparent)`;
|
|
79
|
+
out[`--wr-border-glow-input-color${GLOW_VAR_SUFFIXES[i]}`] = parts ? `hsl(${base} / ${alpha}%)` : `color-mix(in srgb, ${base} ${alpha}%, transparent)`;
|
|
80
80
|
}
|
|
81
81
|
return out;
|
|
82
82
|
}
|
|
@@ -87,7 +87,7 @@ function buildGradientVars(colors) {
|
|
|
87
87
|
const colour = palette[Math.min(GRADIENT_COLOR_INDEX[i], palette.length - 1)];
|
|
88
88
|
out[GRADIENT_VAR_NAMES[i]] = `radial-gradient(at ${GRADIENT_POSITIONS[i]}, ${colour} 0px, transparent 50%)`;
|
|
89
89
|
}
|
|
90
|
-
out["--wr-border-glow-gradient-base"] = `linear-gradient(${palette[0]} 0 100%)`;
|
|
90
|
+
out["--wr-border-glow-input-gradient-base"] = `linear-gradient(${palette[0]} 0 100%)`;
|
|
91
91
|
return out;
|
|
92
92
|
}
|
|
93
93
|
function animateValue(opts) {
|
|
@@ -157,33 +157,24 @@ var WrBorderGlow = class WrBorderGlow {
|
|
|
157
157
|
isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
158
158
|
pointerInside = false;
|
|
159
159
|
cssVars = computed(() => {
|
|
160
|
-
const vars = {
|
|
161
|
-
"--wr-border-glow-radius": `${this.borderRadius()}px`,
|
|
162
|
-
"--wr-border-glow-padding": `${this.glowRadius()}px`,
|
|
163
|
-
"--wr-border-glow-cone-spread": String(this.coneSpread()),
|
|
164
|
-
"--wr-border-glow-edge-sensitivity": String(this.edgeSensitivity()),
|
|
165
|
-
"--wr-border-glow-fill-opacity": String(this.fillOpacity())
|
|
166
|
-
};
|
|
160
|
+
const vars = {};
|
|
167
161
|
const colors = this.colors();
|
|
168
162
|
if (colors !== null) Object.assign(vars, buildGradientVars(colors));
|
|
169
163
|
const bg = this.backgroundColor();
|
|
170
|
-
if (bg !== null && isSafeCssValue(bg)) vars["--wr-border-glow-bg"] = bg;
|
|
164
|
+
if (bg !== null && isSafeCssValue(bg)) vars["--wr-border-glow-input-bg"] = bg;
|
|
171
165
|
const glow = this.glowColor();
|
|
172
166
|
const glowVars = glow === null ? null : buildGlowVars(glow, this.glowIntensity());
|
|
173
167
|
if (glowVars !== null) Object.assign(vars, glowVars);
|
|
174
168
|
return vars;
|
|
175
169
|
}, ...ngDevMode ? [{ debugName: "cssVars" }] : /* istanbul ignore next */ []);
|
|
176
170
|
constructor() {
|
|
177
|
-
let written =
|
|
171
|
+
let written = [];
|
|
178
172
|
effect(() => {
|
|
179
173
|
const { style } = this.el.nativeElement;
|
|
180
174
|
const vars = this.cssVars();
|
|
181
|
-
for (const
|
|
182
|
-
written =
|
|
183
|
-
for (const
|
|
184
|
-
style.setProperty(k, v);
|
|
185
|
-
written[k] = style.getPropertyValue(k);
|
|
186
|
-
}
|
|
175
|
+
for (const k of written) if (!Object.hasOwn(vars, k)) style.removeProperty(k);
|
|
176
|
+
written = Object.keys(vars);
|
|
177
|
+
for (const k of written) style.setProperty(k, vars[k]);
|
|
187
178
|
});
|
|
188
179
|
if (!this.isBrowser) return;
|
|
189
180
|
effect((onCleanup) => {
|
|
@@ -425,11 +416,18 @@ var WrBorderGlow = class WrBorderGlow {
|
|
|
425
416
|
"pointermove": "onPointerMove($event)",
|
|
426
417
|
"pointerleave": "onPointerLeave()"
|
|
427
418
|
},
|
|
419
|
+
properties: {
|
|
420
|
+
"style.--wr-border-glow-radius.px": "borderRadius()",
|
|
421
|
+
"style.--wr-border-glow-padding.px": "glowRadius()",
|
|
422
|
+
"style.--wr-border-glow-cone-spread": "coneSpread()",
|
|
423
|
+
"style.--wr-border-glow-edge-sensitivity": "edgeSensitivity()",
|
|
424
|
+
"style.--wr-border-glow-fill-opacity": "fillOpacity()"
|
|
425
|
+
},
|
|
428
426
|
classAttribute: "wr-border-glow"
|
|
429
427
|
},
|
|
430
428
|
ngImport: i0,
|
|
431
429
|
template: "<span class=\"wr-border-glow__edge\" aria-hidden=\"true\"></span>\n<div class=\"wr-border-glow__inner\">\n <ng-content />\n</div>\n",
|
|
432
|
-
styles: [".wr-border-glow{--wr-border-glow-edge-proximity: 0;--wr-border-glow-angle: 45deg;--wr-border-glow-color: light-dark(hsl(40deg 90% 45% / 100%), hsl(40deg 80% 80% / 100%));--wr-border-glow-color-60: light-dark(hsl(40deg 90% 45% / 60%), hsl(40deg 80% 80% / 60%));--wr-border-glow-color-50: light-dark(hsl(40deg 90% 45% / 50%), hsl(40deg 80% 80% / 50%));--wr-border-glow-color-40: light-dark(hsl(40deg 90% 45% / 40%), hsl(40deg 80% 80% / 40%));--wr-border-glow-color-30: light-dark(hsl(40deg 90% 45% / 30%), hsl(40deg 80% 80% / 30%));--wr-border-glow-color-20: light-dark(hsl(40deg 90% 45% / 20%), hsl(40deg 80% 80% / 20%));--wr-border-glow-color-10: light-dark(hsl(40deg 90% 45% / 10%), hsl(40deg 80% 80% / 10%));--wr-border-glow-gradient-one: radial-gradient( at 80% 55%, light-dark(hsl(268deg 80% 50%), hsl(268deg 100% 76%)) 0px, transparent 50% );--wr-border-glow-gradient-two: radial-gradient( at 69% 34%, light-dark(hsl(349deg 85% 52%), hsl(349deg 100% 74%)) 0px, transparent 50% );--wr-border-glow-gradient-three: radial-gradient( at 8% 6%, light-dark(hsl(136deg 70% 38%), hsl(136deg 100% 78%)) 0px, transparent 50% );--wr-border-glow-gradient-four: radial-gradient( at 41% 38%, light-dark(hsl(192deg 90% 40%), hsl(192deg 100% 64%)) 0px, transparent 50% );--wr-border-glow-gradient-five: radial-gradient( at 86% 85%, light-dark(hsl(186deg 90% 38%), hsl(186deg 100% 74%)) 0px, transparent 50% );--wr-border-glow-gradient-six: radial-gradient( at 82% 18%, light-dark(hsl(40deg 95% 45%), hsl(52deg 100% 65%)) 0px, transparent 50% );--wr-border-glow-gradient-seven: radial-gradient( at 51% 4%, light-dark(hsl(12deg 90% 50%), hsl(12deg 100% 72%)) 0px, transparent 50% );--wr-border-glow-gradient-base: linear-gradient(light-dark(hsl(262deg 70% 50%), #c299ff) 0 100%);--wr-border-glow-outline: light-dark(var(--wr-border-base), rgb(255 255 255 / 15%));--wr-border-glow-edge-shadow: inset 0 0 0 1px var(--wr-border-glow-color), inset 0 0 1px 0 light-dark(transparent, var(--wr-border-glow-color-60)), inset 0 0 3px 0 light-dark(transparent, var(--wr-border-glow-color-50)), inset 0 0 6px 0 light-dark(transparent, var(--wr-border-glow-color-40)), inset 0 0 15px 0 light-dark(transparent, var(--wr-border-glow-color-30)), inset 0 0 25px 2px light-dark(transparent, var(--wr-border-glow-color-20)), inset 0 0 50px 2px light-dark(transparent, var(--wr-border-glow-color-10)), 0 0 1px 0 var(--wr-border-glow-color-60), 0 0 3px 0 var(--wr-border-glow-color-50), 0 0 6px 0 var(--wr-border-glow-color-40), 0 0 15px 0 var(--wr-border-glow-color-30), 0 0 25px 2px var(--wr-border-glow-color-20), 0 0 50px 2px var(--wr-border-glow-color-10);--wr-border-glow-halo-blend: normal;--wr-border-glow-fill-blend: normal;--wr-border-glow-fill-scale: .25;--wr-border-glow-edge-sensitivity: 30;--wr-border-glow-color-sensitivity: calc(var(--wr-border-glow-edge-sensitivity) + 20);--wr-border-glow-radius: 28px;--wr-border-glow-padding: 40px;--wr-border-glow-cone-spread: 25;position:relative;display:grid;border-radius:var(--wr-border-glow-radius);background:var(--wr-border-glow-bg, var(--wr-
|
|
430
|
+
styles: [".wr-border-glow{--wr-border-glow-edge-proximity: 0;--wr-border-glow-angle: 45deg;--wr-border-glow-bg: var(--wr-color-surface);--wr-border-glow-input-bg: initial;--wr-border-glow-input-color: initial;--wr-border-glow-input-color-60: initial;--wr-border-glow-input-color-50: initial;--wr-border-glow-input-color-40: initial;--wr-border-glow-input-color-30: initial;--wr-border-glow-input-color-20: initial;--wr-border-glow-input-color-10: initial;--wr-border-glow-input-gradient-one: initial;--wr-border-glow-input-gradient-two: initial;--wr-border-glow-input-gradient-three: initial;--wr-border-glow-input-gradient-four: initial;--wr-border-glow-input-gradient-five: initial;--wr-border-glow-input-gradient-six: initial;--wr-border-glow-input-gradient-seven: initial;--wr-border-glow-input-gradient-base: initial;--wr-border-glow-color: light-dark(hsl(40deg 90% 45% / 100%), hsl(40deg 80% 80% / 100%));--wr-border-glow-color-60: light-dark(hsl(40deg 90% 45% / 60%), hsl(40deg 80% 80% / 60%));--wr-border-glow-color-50: light-dark(hsl(40deg 90% 45% / 50%), hsl(40deg 80% 80% / 50%));--wr-border-glow-color-40: light-dark(hsl(40deg 90% 45% / 40%), hsl(40deg 80% 80% / 40%));--wr-border-glow-color-30: light-dark(hsl(40deg 90% 45% / 30%), hsl(40deg 80% 80% / 30%));--wr-border-glow-color-20: light-dark(hsl(40deg 90% 45% / 20%), hsl(40deg 80% 80% / 20%));--wr-border-glow-color-10: light-dark(hsl(40deg 90% 45% / 10%), hsl(40deg 80% 80% / 10%));--wr-border-glow-gradient-one: radial-gradient( at 80% 55%, light-dark(hsl(268deg 80% 50%), hsl(268deg 100% 76%)) 0px, transparent 50% );--wr-border-glow-gradient-two: radial-gradient( at 69% 34%, light-dark(hsl(349deg 85% 52%), hsl(349deg 100% 74%)) 0px, transparent 50% );--wr-border-glow-gradient-three: radial-gradient( at 8% 6%, light-dark(hsl(136deg 70% 38%), hsl(136deg 100% 78%)) 0px, transparent 50% );--wr-border-glow-gradient-four: radial-gradient( at 41% 38%, light-dark(hsl(192deg 90% 40%), hsl(192deg 100% 64%)) 0px, transparent 50% );--wr-border-glow-gradient-five: radial-gradient( at 86% 85%, light-dark(hsl(186deg 90% 38%), hsl(186deg 100% 74%)) 0px, transparent 50% );--wr-border-glow-gradient-six: radial-gradient( at 82% 18%, light-dark(hsl(40deg 95% 45%), hsl(52deg 100% 65%)) 0px, transparent 50% );--wr-border-glow-gradient-seven: radial-gradient( at 51% 4%, light-dark(hsl(12deg 90% 50%), hsl(12deg 100% 72%)) 0px, transparent 50% );--wr-border-glow-gradient-base: linear-gradient(light-dark(hsl(262deg 70% 50%), #c299ff) 0 100%);--wr-border-glow-outline: light-dark(var(--wr-border-base), rgb(255 255 255 / 15%));--wr-border-glow-edge-shadow: inset 0 0 0 1px var(--wr-border-glow-input-color, var(--wr-border-glow-color)), inset 0 0 1px 0 light-dark(transparent, var(--wr-border-glow-input-color-60, var(--wr-border-glow-color-60))), inset 0 0 3px 0 light-dark(transparent, var(--wr-border-glow-input-color-50, var(--wr-border-glow-color-50))), inset 0 0 6px 0 light-dark(transparent, var(--wr-border-glow-input-color-40, var(--wr-border-glow-color-40))), inset 0 0 15px 0 light-dark(transparent, var(--wr-border-glow-input-color-30, var(--wr-border-glow-color-30))), inset 0 0 25px 2px light-dark(transparent, var(--wr-border-glow-input-color-20, var(--wr-border-glow-color-20))), inset 0 0 50px 2px light-dark(transparent, var(--wr-border-glow-input-color-10, var(--wr-border-glow-color-10))), 0 0 1px 0 var(--wr-border-glow-input-color-60, var(--wr-border-glow-color-60)), 0 0 3px 0 var(--wr-border-glow-input-color-50, var(--wr-border-glow-color-50)), 0 0 6px 0 var(--wr-border-glow-input-color-40, var(--wr-border-glow-color-40)), 0 0 15px 0 var(--wr-border-glow-input-color-30, var(--wr-border-glow-color-30)), 0 0 25px 2px var(--wr-border-glow-input-color-20, var(--wr-border-glow-color-20)), 0 0 50px 2px var(--wr-border-glow-input-color-10, var(--wr-border-glow-color-10));--wr-border-glow-halo-blend: normal;--wr-border-glow-fill-blend: normal;--wr-border-glow-fill-scale: .25;--wr-border-glow-edge-sensitivity: 30;--wr-border-glow-color-sensitivity: calc(var(--wr-border-glow-edge-sensitivity) + 20);--wr-border-glow-radius: 28px;--wr-border-glow-padding: 40px;--wr-border-glow-cone-spread: 25;position:relative;display:grid;border-radius:var(--wr-border-glow-radius);background:var(--wr-border-glow-input-bg, var(--wr-border-glow-bg));overflow:visible;isolation:isolate;transform:translateZ(.01px);box-shadow:inset 0 0 0 1px var(--wr-border-glow-outline)}.wr-border-glow:before,.wr-border-glow:after,.wr-border-glow>.wr-border-glow__edge{content:\"\";position:absolute;inset:0;border-radius:inherit;transition:opacity .25s ease-out;z-index:-1}.wr-border-glow:not(:hover):not(.wr-border-glow--sweeping):not(.wr-border-glow--looping):before,.wr-border-glow:not(:hover):not(.wr-border-glow--sweeping):not(.wr-border-glow--looping):after,.wr-border-glow:not(:hover):not(.wr-border-glow--sweeping):not(.wr-border-glow--looping)>.wr-border-glow__edge{opacity:0;transition:opacity .75s ease-in-out}.wr-border-glow:before{border:1px solid transparent;background:linear-gradient(var(--wr-border-glow-input-bg, var(--wr-border-glow-bg)) 0 100%) padding-box,linear-gradient(#fff0 0 100%) border-box,var(--wr-border-glow-input-gradient-one, var(--wr-border-glow-gradient-one)) border-box,var(--wr-border-glow-input-gradient-two, var(--wr-border-glow-gradient-two)) border-box,var(--wr-border-glow-input-gradient-three, var(--wr-border-glow-gradient-three)) border-box,var(--wr-border-glow-input-gradient-four, var(--wr-border-glow-gradient-four)) border-box,var(--wr-border-glow-input-gradient-five, var(--wr-border-glow-gradient-five)) border-box,var(--wr-border-glow-input-gradient-six, var(--wr-border-glow-gradient-six)) border-box,var(--wr-border-glow-input-gradient-seven, var(--wr-border-glow-gradient-seven)) border-box,var(--wr-border-glow-input-gradient-base, var(--wr-border-glow-gradient-base)) border-box;opacity:calc((var(--wr-border-glow-edge-proximity) - var(--wr-border-glow-color-sensitivity)) / (100 - var(--wr-border-glow-color-sensitivity)));-webkit-mask-image:conic-gradient(from var(--wr-border-glow-angle) at center,black calc(var(--wr-border-glow-cone-spread) * 1%),transparent calc((var(--wr-border-glow-cone-spread) + 15) * 1%),transparent calc((100 - var(--wr-border-glow-cone-spread) - 15) * 1%),black calc((100 - var(--wr-border-glow-cone-spread)) * 1%));mask-image:conic-gradient(from var(--wr-border-glow-angle) at center,black calc(var(--wr-border-glow-cone-spread) * 1%),transparent calc((var(--wr-border-glow-cone-spread) + 15) * 1%),transparent calc((100 - var(--wr-border-glow-cone-spread) - 15) * 1%),black calc((100 - var(--wr-border-glow-cone-spread)) * 1%))}.wr-border-glow:after{border:1px solid transparent;background:var(--wr-border-glow-input-gradient-one, var(--wr-border-glow-gradient-one)) padding-box,var(--wr-border-glow-input-gradient-two, var(--wr-border-glow-gradient-two)) padding-box,var(--wr-border-glow-input-gradient-three, var(--wr-border-glow-gradient-three)) padding-box,var(--wr-border-glow-input-gradient-four, var(--wr-border-glow-gradient-four)) padding-box,var(--wr-border-glow-input-gradient-five, var(--wr-border-glow-gradient-five)) padding-box,var(--wr-border-glow-input-gradient-six, var(--wr-border-glow-gradient-six)) padding-box,var(--wr-border-glow-input-gradient-seven, var(--wr-border-glow-gradient-seven)) padding-box,var(--wr-border-glow-input-gradient-base, var(--wr-border-glow-gradient-base)) padding-box;-webkit-mask-image:linear-gradient(to bottom,#000,#000),radial-gradient(ellipse at 50% 50%,black 40%,transparent 65%),radial-gradient(ellipse at 66% 66%,black 5%,transparent 40%),radial-gradient(ellipse at 33% 33%,black 5%,transparent 40%),radial-gradient(ellipse at 66% 33%,black 5%,transparent 40%),radial-gradient(ellipse at 33% 66%,black 5%,transparent 40%),conic-gradient(from var(--wr-border-glow-angle) at center,transparent 5%,black 15%,black 85%,transparent 95%);mask-image:linear-gradient(to bottom,#000,#000),radial-gradient(ellipse at 50% 50%,black 40%,transparent 65%),radial-gradient(ellipse at 66% 66%,black 5%,transparent 40%),radial-gradient(ellipse at 33% 33%,black 5%,transparent 40%),radial-gradient(ellipse at 66% 33%,black 5%,transparent 40%),radial-gradient(ellipse at 33% 66%,black 5%,transparent 40%),conic-gradient(from var(--wr-border-glow-angle) at center,transparent 5%,black 15%,black 85%,transparent 95%);-webkit-mask-composite:source-out,source-over,source-over,source-over,source-over,source-over;mask-composite:subtract,add,add,add,add,add;opacity:calc(var(--wr-border-glow-fill-opacity, .5) * var(--wr-border-glow-fill-scale) * (var(--wr-border-glow-edge-proximity) - var(--wr-border-glow-color-sensitivity)) / (100 - var(--wr-border-glow-color-sensitivity)));mix-blend-mode:var(--wr-border-glow-fill-blend)}.wr-border-glow>.wr-border-glow__edge{inset:calc(var(--wr-border-glow-padding) * -1);pointer-events:none;z-index:1;-webkit-mask-image:conic-gradient(from var(--wr-border-glow-angle) at center,black 2.5%,transparent 10%,transparent 90%,black 97.5%);mask-image:conic-gradient(from var(--wr-border-glow-angle) at center,black 2.5%,transparent 10%,transparent 90%,black 97.5%);opacity:calc((var(--wr-border-glow-edge-proximity) - var(--wr-border-glow-edge-sensitivity)) / (100 - var(--wr-border-glow-edge-sensitivity)));mix-blend-mode:var(--wr-border-glow-halo-blend)}.wr-border-glow>.wr-border-glow__edge:before{content:\"\";position:absolute;inset:var(--wr-border-glow-padding);border-radius:inherit;box-shadow:var(--wr-border-glow-edge-shadow)}.wr-border-glow__inner{position:relative;z-index:1;display:flex;flex-direction:column}\n"],
|
|
433
431
|
encapsulation: i0.ViewEncapsulation.None
|
|
434
432
|
});
|
|
435
433
|
};
|
|
@@ -445,12 +443,17 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
445
443
|
encapsulation: ViewEncapsulation.None,
|
|
446
444
|
host: {
|
|
447
445
|
class: "wr-border-glow",
|
|
446
|
+
"[style.--wr-border-glow-radius.px]": "borderRadius()",
|
|
447
|
+
"[style.--wr-border-glow-padding.px]": "glowRadius()",
|
|
448
|
+
"[style.--wr-border-glow-cone-spread]": "coneSpread()",
|
|
449
|
+
"[style.--wr-border-glow-edge-sensitivity]": "edgeSensitivity()",
|
|
450
|
+
"[style.--wr-border-glow-fill-opacity]": "fillOpacity()",
|
|
448
451
|
"(pointerenter)": "onPointerEnter()",
|
|
449
452
|
"(pointermove)": "onPointerMove($event)",
|
|
450
453
|
"(pointerleave)": "onPointerLeave()"
|
|
451
454
|
},
|
|
452
455
|
template: "<span class=\"wr-border-glow__edge\" aria-hidden=\"true\"></span>\n<div class=\"wr-border-glow__inner\">\n <ng-content />\n</div>\n",
|
|
453
|
-
styles: [".wr-border-glow{--wr-border-glow-edge-proximity: 0;--wr-border-glow-angle: 45deg;--wr-border-glow-color: light-dark(hsl(40deg 90% 45% / 100%), hsl(40deg 80% 80% / 100%));--wr-border-glow-color-60: light-dark(hsl(40deg 90% 45% / 60%), hsl(40deg 80% 80% / 60%));--wr-border-glow-color-50: light-dark(hsl(40deg 90% 45% / 50%), hsl(40deg 80% 80% / 50%));--wr-border-glow-color-40: light-dark(hsl(40deg 90% 45% / 40%), hsl(40deg 80% 80% / 40%));--wr-border-glow-color-30: light-dark(hsl(40deg 90% 45% / 30%), hsl(40deg 80% 80% / 30%));--wr-border-glow-color-20: light-dark(hsl(40deg 90% 45% / 20%), hsl(40deg 80% 80% / 20%));--wr-border-glow-color-10: light-dark(hsl(40deg 90% 45% / 10%), hsl(40deg 80% 80% / 10%));--wr-border-glow-gradient-one: radial-gradient( at 80% 55%, light-dark(hsl(268deg 80% 50%), hsl(268deg 100% 76%)) 0px, transparent 50% );--wr-border-glow-gradient-two: radial-gradient( at 69% 34%, light-dark(hsl(349deg 85% 52%), hsl(349deg 100% 74%)) 0px, transparent 50% );--wr-border-glow-gradient-three: radial-gradient( at 8% 6%, light-dark(hsl(136deg 70% 38%), hsl(136deg 100% 78%)) 0px, transparent 50% );--wr-border-glow-gradient-four: radial-gradient( at 41% 38%, light-dark(hsl(192deg 90% 40%), hsl(192deg 100% 64%)) 0px, transparent 50% );--wr-border-glow-gradient-five: radial-gradient( at 86% 85%, light-dark(hsl(186deg 90% 38%), hsl(186deg 100% 74%)) 0px, transparent 50% );--wr-border-glow-gradient-six: radial-gradient( at 82% 18%, light-dark(hsl(40deg 95% 45%), hsl(52deg 100% 65%)) 0px, transparent 50% );--wr-border-glow-gradient-seven: radial-gradient( at 51% 4%, light-dark(hsl(12deg 90% 50%), hsl(12deg 100% 72%)) 0px, transparent 50% );--wr-border-glow-gradient-base: linear-gradient(light-dark(hsl(262deg 70% 50%), #c299ff) 0 100%);--wr-border-glow-outline: light-dark(var(--wr-border-base), rgb(255 255 255 / 15%));--wr-border-glow-edge-shadow: inset 0 0 0 1px var(--wr-border-glow-color), inset 0 0 1px 0 light-dark(transparent, var(--wr-border-glow-color-60)), inset 0 0 3px 0 light-dark(transparent, var(--wr-border-glow-color-50)), inset 0 0 6px 0 light-dark(transparent, var(--wr-border-glow-color-40)), inset 0 0 15px 0 light-dark(transparent, var(--wr-border-glow-color-30)), inset 0 0 25px 2px light-dark(transparent, var(--wr-border-glow-color-20)), inset 0 0 50px 2px light-dark(transparent, var(--wr-border-glow-color-10)), 0 0 1px 0 var(--wr-border-glow-color-60), 0 0 3px 0 var(--wr-border-glow-color-50), 0 0 6px 0 var(--wr-border-glow-color-40), 0 0 15px 0 var(--wr-border-glow-color-30), 0 0 25px 2px var(--wr-border-glow-color-20), 0 0 50px 2px var(--wr-border-glow-color-10);--wr-border-glow-halo-blend: normal;--wr-border-glow-fill-blend: normal;--wr-border-glow-fill-scale: .25;--wr-border-glow-edge-sensitivity: 30;--wr-border-glow-color-sensitivity: calc(var(--wr-border-glow-edge-sensitivity) + 20);--wr-border-glow-radius: 28px;--wr-border-glow-padding: 40px;--wr-border-glow-cone-spread: 25;position:relative;display:grid;border-radius:var(--wr-border-glow-radius);background:var(--wr-border-glow-bg, var(--wr-
|
|
456
|
+
styles: [".wr-border-glow{--wr-border-glow-edge-proximity: 0;--wr-border-glow-angle: 45deg;--wr-border-glow-bg: var(--wr-color-surface);--wr-border-glow-input-bg: initial;--wr-border-glow-input-color: initial;--wr-border-glow-input-color-60: initial;--wr-border-glow-input-color-50: initial;--wr-border-glow-input-color-40: initial;--wr-border-glow-input-color-30: initial;--wr-border-glow-input-color-20: initial;--wr-border-glow-input-color-10: initial;--wr-border-glow-input-gradient-one: initial;--wr-border-glow-input-gradient-two: initial;--wr-border-glow-input-gradient-three: initial;--wr-border-glow-input-gradient-four: initial;--wr-border-glow-input-gradient-five: initial;--wr-border-glow-input-gradient-six: initial;--wr-border-glow-input-gradient-seven: initial;--wr-border-glow-input-gradient-base: initial;--wr-border-glow-color: light-dark(hsl(40deg 90% 45% / 100%), hsl(40deg 80% 80% / 100%));--wr-border-glow-color-60: light-dark(hsl(40deg 90% 45% / 60%), hsl(40deg 80% 80% / 60%));--wr-border-glow-color-50: light-dark(hsl(40deg 90% 45% / 50%), hsl(40deg 80% 80% / 50%));--wr-border-glow-color-40: light-dark(hsl(40deg 90% 45% / 40%), hsl(40deg 80% 80% / 40%));--wr-border-glow-color-30: light-dark(hsl(40deg 90% 45% / 30%), hsl(40deg 80% 80% / 30%));--wr-border-glow-color-20: light-dark(hsl(40deg 90% 45% / 20%), hsl(40deg 80% 80% / 20%));--wr-border-glow-color-10: light-dark(hsl(40deg 90% 45% / 10%), hsl(40deg 80% 80% / 10%));--wr-border-glow-gradient-one: radial-gradient( at 80% 55%, light-dark(hsl(268deg 80% 50%), hsl(268deg 100% 76%)) 0px, transparent 50% );--wr-border-glow-gradient-two: radial-gradient( at 69% 34%, light-dark(hsl(349deg 85% 52%), hsl(349deg 100% 74%)) 0px, transparent 50% );--wr-border-glow-gradient-three: radial-gradient( at 8% 6%, light-dark(hsl(136deg 70% 38%), hsl(136deg 100% 78%)) 0px, transparent 50% );--wr-border-glow-gradient-four: radial-gradient( at 41% 38%, light-dark(hsl(192deg 90% 40%), hsl(192deg 100% 64%)) 0px, transparent 50% );--wr-border-glow-gradient-five: radial-gradient( at 86% 85%, light-dark(hsl(186deg 90% 38%), hsl(186deg 100% 74%)) 0px, transparent 50% );--wr-border-glow-gradient-six: radial-gradient( at 82% 18%, light-dark(hsl(40deg 95% 45%), hsl(52deg 100% 65%)) 0px, transparent 50% );--wr-border-glow-gradient-seven: radial-gradient( at 51% 4%, light-dark(hsl(12deg 90% 50%), hsl(12deg 100% 72%)) 0px, transparent 50% );--wr-border-glow-gradient-base: linear-gradient(light-dark(hsl(262deg 70% 50%), #c299ff) 0 100%);--wr-border-glow-outline: light-dark(var(--wr-border-base), rgb(255 255 255 / 15%));--wr-border-glow-edge-shadow: inset 0 0 0 1px var(--wr-border-glow-input-color, var(--wr-border-glow-color)), inset 0 0 1px 0 light-dark(transparent, var(--wr-border-glow-input-color-60, var(--wr-border-glow-color-60))), inset 0 0 3px 0 light-dark(transparent, var(--wr-border-glow-input-color-50, var(--wr-border-glow-color-50))), inset 0 0 6px 0 light-dark(transparent, var(--wr-border-glow-input-color-40, var(--wr-border-glow-color-40))), inset 0 0 15px 0 light-dark(transparent, var(--wr-border-glow-input-color-30, var(--wr-border-glow-color-30))), inset 0 0 25px 2px light-dark(transparent, var(--wr-border-glow-input-color-20, var(--wr-border-glow-color-20))), inset 0 0 50px 2px light-dark(transparent, var(--wr-border-glow-input-color-10, var(--wr-border-glow-color-10))), 0 0 1px 0 var(--wr-border-glow-input-color-60, var(--wr-border-glow-color-60)), 0 0 3px 0 var(--wr-border-glow-input-color-50, var(--wr-border-glow-color-50)), 0 0 6px 0 var(--wr-border-glow-input-color-40, var(--wr-border-glow-color-40)), 0 0 15px 0 var(--wr-border-glow-input-color-30, var(--wr-border-glow-color-30)), 0 0 25px 2px var(--wr-border-glow-input-color-20, var(--wr-border-glow-color-20)), 0 0 50px 2px var(--wr-border-glow-input-color-10, var(--wr-border-glow-color-10));--wr-border-glow-halo-blend: normal;--wr-border-glow-fill-blend: normal;--wr-border-glow-fill-scale: .25;--wr-border-glow-edge-sensitivity: 30;--wr-border-glow-color-sensitivity: calc(var(--wr-border-glow-edge-sensitivity) + 20);--wr-border-glow-radius: 28px;--wr-border-glow-padding: 40px;--wr-border-glow-cone-spread: 25;position:relative;display:grid;border-radius:var(--wr-border-glow-radius);background:var(--wr-border-glow-input-bg, var(--wr-border-glow-bg));overflow:visible;isolation:isolate;transform:translateZ(.01px);box-shadow:inset 0 0 0 1px var(--wr-border-glow-outline)}.wr-border-glow:before,.wr-border-glow:after,.wr-border-glow>.wr-border-glow__edge{content:\"\";position:absolute;inset:0;border-radius:inherit;transition:opacity .25s ease-out;z-index:-1}.wr-border-glow:not(:hover):not(.wr-border-glow--sweeping):not(.wr-border-glow--looping):before,.wr-border-glow:not(:hover):not(.wr-border-glow--sweeping):not(.wr-border-glow--looping):after,.wr-border-glow:not(:hover):not(.wr-border-glow--sweeping):not(.wr-border-glow--looping)>.wr-border-glow__edge{opacity:0;transition:opacity .75s ease-in-out}.wr-border-glow:before{border:1px solid transparent;background:linear-gradient(var(--wr-border-glow-input-bg, var(--wr-border-glow-bg)) 0 100%) padding-box,linear-gradient(#fff0 0 100%) border-box,var(--wr-border-glow-input-gradient-one, var(--wr-border-glow-gradient-one)) border-box,var(--wr-border-glow-input-gradient-two, var(--wr-border-glow-gradient-two)) border-box,var(--wr-border-glow-input-gradient-three, var(--wr-border-glow-gradient-three)) border-box,var(--wr-border-glow-input-gradient-four, var(--wr-border-glow-gradient-four)) border-box,var(--wr-border-glow-input-gradient-five, var(--wr-border-glow-gradient-five)) border-box,var(--wr-border-glow-input-gradient-six, var(--wr-border-glow-gradient-six)) border-box,var(--wr-border-glow-input-gradient-seven, var(--wr-border-glow-gradient-seven)) border-box,var(--wr-border-glow-input-gradient-base, var(--wr-border-glow-gradient-base)) border-box;opacity:calc((var(--wr-border-glow-edge-proximity) - var(--wr-border-glow-color-sensitivity)) / (100 - var(--wr-border-glow-color-sensitivity)));-webkit-mask-image:conic-gradient(from var(--wr-border-glow-angle) at center,black calc(var(--wr-border-glow-cone-spread) * 1%),transparent calc((var(--wr-border-glow-cone-spread) + 15) * 1%),transparent calc((100 - var(--wr-border-glow-cone-spread) - 15) * 1%),black calc((100 - var(--wr-border-glow-cone-spread)) * 1%));mask-image:conic-gradient(from var(--wr-border-glow-angle) at center,black calc(var(--wr-border-glow-cone-spread) * 1%),transparent calc((var(--wr-border-glow-cone-spread) + 15) * 1%),transparent calc((100 - var(--wr-border-glow-cone-spread) - 15) * 1%),black calc((100 - var(--wr-border-glow-cone-spread)) * 1%))}.wr-border-glow:after{border:1px solid transparent;background:var(--wr-border-glow-input-gradient-one, var(--wr-border-glow-gradient-one)) padding-box,var(--wr-border-glow-input-gradient-two, var(--wr-border-glow-gradient-two)) padding-box,var(--wr-border-glow-input-gradient-three, var(--wr-border-glow-gradient-three)) padding-box,var(--wr-border-glow-input-gradient-four, var(--wr-border-glow-gradient-four)) padding-box,var(--wr-border-glow-input-gradient-five, var(--wr-border-glow-gradient-five)) padding-box,var(--wr-border-glow-input-gradient-six, var(--wr-border-glow-gradient-six)) padding-box,var(--wr-border-glow-input-gradient-seven, var(--wr-border-glow-gradient-seven)) padding-box,var(--wr-border-glow-input-gradient-base, var(--wr-border-glow-gradient-base)) padding-box;-webkit-mask-image:linear-gradient(to bottom,#000,#000),radial-gradient(ellipse at 50% 50%,black 40%,transparent 65%),radial-gradient(ellipse at 66% 66%,black 5%,transparent 40%),radial-gradient(ellipse at 33% 33%,black 5%,transparent 40%),radial-gradient(ellipse at 66% 33%,black 5%,transparent 40%),radial-gradient(ellipse at 33% 66%,black 5%,transparent 40%),conic-gradient(from var(--wr-border-glow-angle) at center,transparent 5%,black 15%,black 85%,transparent 95%);mask-image:linear-gradient(to bottom,#000,#000),radial-gradient(ellipse at 50% 50%,black 40%,transparent 65%),radial-gradient(ellipse at 66% 66%,black 5%,transparent 40%),radial-gradient(ellipse at 33% 33%,black 5%,transparent 40%),radial-gradient(ellipse at 66% 33%,black 5%,transparent 40%),radial-gradient(ellipse at 33% 66%,black 5%,transparent 40%),conic-gradient(from var(--wr-border-glow-angle) at center,transparent 5%,black 15%,black 85%,transparent 95%);-webkit-mask-composite:source-out,source-over,source-over,source-over,source-over,source-over;mask-composite:subtract,add,add,add,add,add;opacity:calc(var(--wr-border-glow-fill-opacity, .5) * var(--wr-border-glow-fill-scale) * (var(--wr-border-glow-edge-proximity) - var(--wr-border-glow-color-sensitivity)) / (100 - var(--wr-border-glow-color-sensitivity)));mix-blend-mode:var(--wr-border-glow-fill-blend)}.wr-border-glow>.wr-border-glow__edge{inset:calc(var(--wr-border-glow-padding) * -1);pointer-events:none;z-index:1;-webkit-mask-image:conic-gradient(from var(--wr-border-glow-angle) at center,black 2.5%,transparent 10%,transparent 90%,black 97.5%);mask-image:conic-gradient(from var(--wr-border-glow-angle) at center,black 2.5%,transparent 10%,transparent 90%,black 97.5%);opacity:calc((var(--wr-border-glow-edge-proximity) - var(--wr-border-glow-edge-sensitivity)) / (100 - var(--wr-border-glow-edge-sensitivity)));mix-blend-mode:var(--wr-border-glow-halo-blend)}.wr-border-glow>.wr-border-glow__edge:before{content:\"\";position:absolute;inset:var(--wr-border-glow-padding);border-radius:inherit;box-shadow:var(--wr-border-glow-edge-shadow)}.wr-border-glow__inner{position:relative;z-index:1;display:flex;flex-direction:column}\n"]
|
|
454
457
|
}]
|
|
455
458
|
}],
|
|
456
459
|
ctorParameters: () => [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-bits-border-glow.mjs","names":[],"sources":["../../../projects/lib/bits/border-glow/border-glow.ts","../../../projects/lib/bits/border-glow/border-glow.html"],"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, an optional auto-sweep on mount and an optional endless\n * loop.\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\n/**\n * Where a looping cone rests under reduced motion, and where it starts when\n * nothing has written an angle yet — the stylesheet's own default.\n */\nconst REST_ANGLE = 45;\n\n/**\n * The most frame time one loop step may count, in ms. A hidden tab, a long task\n * or a debugger stops frames without telling the loop, and the first frame back\n * would otherwise jump the cone by the whole time away.\n */\nconst MAX_LOOP_STEP_MS = 100;\n\n/** `'40 80 80'`, `'268 100% 76%'`, `'40deg 80% 80%'` — the bare HSL parts `glowColor` has always taken. */\nconst HSL_PARTS = /^\\s*([\\d.]+)(?:deg)?\\s+([\\d.]+)%?\\s+([\\d.]+)%?\\s*$/;\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> | null {\n const parts = HSL_PARTS.exec(glowColor);\n // Anything else is a CSS colour, interpolated into a `color-mix()` — so it is\n // held to the containment check `colors` and `backgroundColor` pass, and an\n // unsafe value leaves the halo to the theme.\n if (!parts && !isSafeCssValue(glowColor)) return null;\n const base = parts\n ? `${Number.parseFloat(parts[1])}deg ${Number.parseFloat(parts[2])}% ${Number.parseFloat(parts[3])}%`\n : glowColor.trim();\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]}`] = parts\n ? `hsl(${base} / ${alpha}%)`\n : `color-mix(in srgb, ${base} ${alpha}%, transparent)`;\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 bloom near the edges, painted with a normal blend by default and\n * overridable through `--wr-border-glow-fill-blend` (subtle; control the\n * strength via `[fillOpacity]`).\n * - Optional `[animated]` mode performs a one-shot sweep on mount, useful\n * for drawing attention to a freshly-rendered card.\n * - Optional `[loop]` mode circles the perimeter for ever, edge fully lit —\n * a frame for a call to action rather than a reaction to the cursor.\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 '(pointerenter)': 'onPointerEnter()',\n '(pointermove)': 'onPointerMove($event)',\n '(pointerleave)': 'onPointerLeave()',\n },\n})\nexport class WrBorderGlow {\n /**\n * Card background fill. When unset, uses the theme surface token. A hook set inline on\n * the same element is replaced by this input and does not come back when it is unset,\n * so set it from a stylesheet rule. The same goes for `glowColor` and `colors`.\n */\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 /**\n * Halo opacity multiplier (1 = full strength).\n *\n * Applies only alongside an explicit `glowColor`: the halo variables are\n * built from the two together, so on the default palette this input is read\n * and never reaches a declaration.\n *\n * @default 1\n */\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 interior bloom near edges (blend: `--wr-border-glow-fill-blend`). @default 0.5 */\n readonly fillOpacity = input(0.5, { transform: numAttr(0.5) });\n\n /**\n * Halo colour: any CSS colour (`'#3567ff'`, `'var(--wr-color-primary)'`), or\n * the bare `'H S L'` parts (no commas) it has always taken. A value that\n * could escape the `color-mix()` it is composed into is ignored. When unset,\n * the theme decides: a deep amber on light surfaces, pale amber on dark.\n * Note `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 /**\n * Circle the perimeter for ever at a constant pace, edge fully lit. The\n * pointer still drives while it is over the card, and the loop picks up from\n * where it left. Replaces the `animated` sweep; under reduced motion the edge\n * stays lit and still.\n *\n * @default false\n */\n readonly loop = input(false, { transform: coerceBooleanProperty });\n\n /** Seconds per lap while `loop` is on. @default 6 */\n readonly speed = input(6, { transform: numAttr(6) });\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 /** Whether a pointer is over the card — the loop holds still while it drives. */\n private pointerInside = false;\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 const glowVars = glow === null ? null : buildGlowVars(glow, this.glowIntensity());\n if (glowVars !== null) Object.assign(vars, glowVars);\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. What left the bag comes off the host, or an\n // input set and then unset stays over the stylesheet's default for good, but only\n // while the host still holds what this wrote: anything else there is someone else's.\n // Read back rather than kept as written, because the browser trims a custom property.\n let written: Record<string, string> = {};\n effect(() => {\n const { style } = this.el.nativeElement;\n const vars = this.cssVars();\n for (const [k, held] of Object.entries(written)) {\n if (!Object.hasOwn(vars, k) && style.getPropertyValue(k) === held) style.removeProperty(k);\n }\n written = {};\n for (const [k, v] of Object.entries(vars)) {\n style.setProperty(k, v);\n written[k] = style.getPropertyValue(k);\n }\n });\n\n if (!this.isBrowser) return;\n\n // Auto-sweep on mount — fires once when `animated` is true, unless `loop`\n // replaces it.\n effect(onCleanup => {\n if (!this.animated() || this.loop()) return;\n onCleanup(this.runAutoSweep());\n });\n\n // Endless loop. Reduced motion is read here, so a change to the preference\n // swaps the rotation for a still, lit edge and back.\n effect(onCleanup => {\n if (!this.loop()) return;\n onCleanup(this.runLoop(this.platform.prefersReducedMotion()));\n });\n }\n\n protected onPointerEnter(): void {\n this.pointerInside = true;\n }\n\n /** Hands the cone back to the loop, re-lit, from wherever the pointer left it. */\n protected onPointerLeave(): void {\n this.pointerInside = false;\n if (this.loop()) this.el.nativeElement.style.setProperty('--wr-border-glow-edge-proximity', '100');\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 /** Endless perimeter loop at a constant pace. Returns its canceller, like `runAutoSweep`. */\n private runLoop(reducedMotion: boolean): () => void {\n const host = this.el.nativeElement;\n host.classList.add('wr-border-glow--looping');\n host.style.setProperty('--wr-border-glow-edge-proximity', '100');\n\n // Reduced motion: no rotation at all. The edge stays lit and still, so a\n // card that uses it as a frame keeps one.\n if (reducedMotion) {\n host.style.setProperty('--wr-border-glow-angle', `${REST_ANGLE}deg`);\n return () => host.classList.remove('wr-border-glow--looping');\n }\n\n let raf = 0;\n let angle = REST_ANGLE;\n // `null` until there is a frame to measure from: the first frame, and the\n // first after the pointer leaves or the card scrolls back into view, pick up\n // the painted angle instead of jumping by the time that passed.\n let last: number | null = null;\n\n const tick = (now: number): void => {\n raf = requestAnimationFrame(tick);\n if (this.pointerInside) {\n last = null;\n return;\n }\n if (last === null) {\n angle = this.paintedAngle();\n } else {\n // By elapsed time rather than per frame, so the pace is the same at\n // any refresh rate. `speed` is in seconds, the timestamps in ms.\n const lap = this.speed() * 1000;\n const step = Math.min(now - last, MAX_LOOP_STEP_MS);\n if (lap > 0) angle = (angle + (360 * step) / lap) % 360;\n }\n last = now;\n host.style.setProperty('--wr-border-glow-angle', `${angle.toFixed(3)}deg`);\n };\n const start = (): void => {\n if (raf === 0) raf = requestAnimationFrame(tick);\n };\n const pause = (): void => {\n cancelAnimationFrame(raf);\n raf = 0;\n last = null;\n };\n\n // Off screen the loop stops asking for frames. Without an observer (jsdom)\n // it simply runs.\n let observer: IntersectionObserver | null = null;\n if (typeof IntersectionObserver === 'undefined') {\n start();\n } else {\n observer = new IntersectionObserver(entries => (entries[entries.length - 1].isIntersecting ? start() : pause()));\n observer.observe(host);\n }\n\n return () => {\n observer?.disconnect();\n pause();\n host.classList.remove('wr-border-glow--looping');\n };\n }\n\n /** The angle the host paints now — the pointer's, a sweep's or the loop's own. */\n private paintedAngle(): number {\n const angle = Number.parseFloat(this.el.nativeElement.style.getPropertyValue('--wr-border-glow-angle'));\n return Number.isNaN(angle) ? REST_ANGLE : angle;\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"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-bits-border-glow.mjs","names":[],"sources":["../../../projects/lib/bits/border-glow/border-glow.ts","../../../projects/lib/bits/border-glow/border-glow.html"],"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, an optional auto-sweep on mount and an optional endless\n * loop.\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\n/**\n * Where a looping cone rests under reduced motion, and where it starts when\n * nothing has written an angle yet — the stylesheet's own default.\n */\nconst REST_ANGLE = 45;\n\n/**\n * The most frame time one loop step may count, in ms. A hidden tab, a long task\n * or a debugger stops frames without telling the loop, and the first frame back\n * would otherwise jump the cone by the whole time away.\n */\nconst MAX_LOOP_STEP_MS = 100;\n\n/** `'40 80 80'`, `'268 100% 76%'`, `'40deg 80% 80%'` — the bare HSL parts `glowColor` has always taken. */\nconst HSL_PARTS = /^\\s*([\\d.]+)(?:deg)?\\s+([\\d.]+)%?\\s+([\\d.]+)%?\\s*$/;\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;\n// Every name a nullable colour input writes is the `--wr-border-glow-input-*`\n// twin of a hook, never the hook itself: `_rules.scss` reads\n// `var(--wr-border-glow-input-x, var(--wr-border-glow-x))`, so an author's own\n// value on the hook survives the input and is read again once it is unset.\nconst GRADIENT_VAR_NAMES = [\n '--wr-border-glow-input-gradient-one',\n '--wr-border-glow-input-gradient-two',\n '--wr-border-glow-input-gradient-three',\n '--wr-border-glow-input-gradient-four',\n '--wr-border-glow-input-gradient-five',\n '--wr-border-glow-input-gradient-six',\n '--wr-border-glow-input-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> | null {\n const parts = HSL_PARTS.exec(glowColor);\n // Anything else is a CSS colour, interpolated into a `color-mix()` — so it is\n // held to the containment check `colors` and `backgroundColor` pass, and an\n // unsafe value leaves the halo to the theme.\n if (!parts && !isSafeCssValue(glowColor)) return null;\n const base = parts\n ? `${Number.parseFloat(parts[1])}deg ${Number.parseFloat(parts[2])}% ${Number.parseFloat(parts[3])}%`\n : glowColor.trim();\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-input-color${GLOW_VAR_SUFFIXES[i]}`] = parts\n ? `hsl(${base} / ${alpha}%)`\n : `color-mix(in srgb, ${base} ${alpha}%, transparent)`;\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-input-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 bloom near the edges, painted with a normal blend by default and\n * overridable through `--wr-border-glow-fill-blend` (subtle; control the\n * strength via `[fillOpacity]`).\n * - Optional `[animated]` mode performs a one-shot sweep on mount, useful\n * for drawing attention to a freshly-rendered card.\n * - Optional `[loop]` mode circles the perimeter for ever, edge fully lit —\n * a frame for a call to action rather than a reaction to the cursor.\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 // The geometry is written whatever the inputs say — none of these can be\n // unset — so each goes on the host as a binding to the hook's own name, the\n // way `wr-threads` binds `--wr-threads-count`. Bound here rather than through\n // the colour bag below, because `gen:css-vars` reads host bindings and literal\n // `setProperty` names and nothing else: written through the bag, the four\n // hooks the stylesheet declares were documented as a rule's to set while every\n // instance overwrote them inline.\n '[style.--wr-border-glow-radius.px]': 'borderRadius()',\n '[style.--wr-border-glow-padding.px]': 'glowRadius()',\n '[style.--wr-border-glow-cone-spread]': 'coneSpread()',\n '[style.--wr-border-glow-edge-sensitivity]': 'edgeSensitivity()',\n '[style.--wr-border-glow-fill-opacity]': 'fillOpacity()',\n '(pointerenter)': 'onPointerEnter()',\n '(pointermove)': 'onPointerMove($event)',\n '(pointerleave)': 'onPointerLeave()',\n },\n})\nexport class WrBorderGlow {\n /**\n * Card background fill. When unset, `--wr-border-glow-bg` decides, which defaults to\n * the theme surface. Written to `--wr-border-glow-input-bg` rather than to the hook,\n * so a `--wr-border-glow-bg` an author set on the element — inline or from a rule —\n * stays there under this input and is read again when it is unset. The same goes\n * for `glowColor` and `colors`.\n */\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 /**\n * Halo opacity multiplier (1 = full strength).\n *\n * Applies only alongside an explicit `glowColor`: the halo variables are\n * built from the two together, so on the default palette this input is read\n * and never reaches a declaration.\n *\n * @default 1\n */\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 interior bloom near edges (blend: `--wr-border-glow-fill-blend`). @default 0.5 */\n readonly fillOpacity = input(0.5, { transform: numAttr(0.5) });\n\n /**\n * Halo colour: any CSS colour (`'#3567ff'`, `'var(--wr-color-primary)'`), or\n * the bare `'H S L'` parts (no commas) it has always taken. A value that\n * could escape the `color-mix()` it is composed into is ignored. When unset,\n * the theme decides: a deep amber on light surfaces, pale amber on dark.\n * Note `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 /**\n * Circle the perimeter for ever at a constant pace, edge fully lit. The\n * pointer still drives while it is over the card, and the loop picks up from\n * where it left. Replaces the `animated` sweep; under reduced motion the edge\n * stays lit and still.\n *\n * @default false\n */\n readonly loop = input(false, { transform: coerceBooleanProperty });\n\n /** Seconds per lap while `loop` is on. @default 6 */\n readonly speed = input(6, { transform: numAttr(6) });\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 /** Whether a pointer is over the card — the loop holds still while it drives. */\n private pointerInside = false;\n\n /**\n * Inline CSS variable bag — the colour inputs, recomputed when one changes. Every\n * name in it is an `--wr-border-glow-input-*` twin that is there only while its\n * input is set; the geometry, which is always set, is bound in `host`.\n */\n protected readonly cssVars = computed<Record<string, string>>(() => {\n const vars: Record<string, string> = {};\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-input-bg'] = bg;\n const glow = this.glowColor();\n const glowVars = glow === null ? null : buildGlowVars(glow, this.glowIntensity());\n if (glowVars !== null) Object.assign(vars, glowVars);\n return vars;\n });\n\n constructor() {\n // Apply the variable bag to the host on every colour input change. An effect\n // rather than one `host: { '[style.--foo]': '...' }` per name, because a\n // palette is seven names and a glow colour another seven, and a single\n // `setProperty` loop handles the lot. What left the bag comes off the host, or\n // an input set and then unset stays over the stylesheet's default for good.\n // Every name here is an `--wr-border-glow-input-*` twin nothing else declares\n // inline, so removing one takes nothing of the author's: their value sits on\n // the hook itself, which this never writes.\n let written: string[] = [];\n effect(() => {\n const { style } = this.el.nativeElement;\n const vars = this.cssVars();\n for (const k of written) {\n if (!Object.hasOwn(vars, k)) style.removeProperty(k);\n }\n written = Object.keys(vars);\n for (const k of written) style.setProperty(k, vars[k]);\n });\n\n if (!this.isBrowser) return;\n\n // Auto-sweep on mount — fires once when `animated` is true, unless `loop`\n // replaces it.\n effect(onCleanup => {\n if (!this.animated() || this.loop()) return;\n onCleanup(this.runAutoSweep());\n });\n\n // Endless loop. Reduced motion is read here, so a change to the preference\n // swaps the rotation for a still, lit edge and back.\n effect(onCleanup => {\n if (!this.loop()) return;\n onCleanup(this.runLoop(this.platform.prefersReducedMotion()));\n });\n }\n\n protected onPointerEnter(): void {\n this.pointerInside = true;\n }\n\n /** Hands the cone back to the loop, re-lit, from wherever the pointer left it. */\n protected onPointerLeave(): void {\n this.pointerInside = false;\n if (this.loop()) this.el.nativeElement.style.setProperty('--wr-border-glow-edge-proximity', '100');\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 /** Endless perimeter loop at a constant pace. Returns its canceller, like `runAutoSweep`. */\n private runLoop(reducedMotion: boolean): () => void {\n const host = this.el.nativeElement;\n host.classList.add('wr-border-glow--looping');\n host.style.setProperty('--wr-border-glow-edge-proximity', '100');\n\n // Reduced motion: no rotation at all. The edge stays lit and still, so a\n // card that uses it as a frame keeps one.\n if (reducedMotion) {\n host.style.setProperty('--wr-border-glow-angle', `${REST_ANGLE}deg`);\n return () => host.classList.remove('wr-border-glow--looping');\n }\n\n let raf = 0;\n let angle = REST_ANGLE;\n // `null` until there is a frame to measure from: the first frame, and the\n // first after the pointer leaves or the card scrolls back into view, pick up\n // the painted angle instead of jumping by the time that passed.\n let last: number | null = null;\n\n const tick = (now: number): void => {\n raf = requestAnimationFrame(tick);\n if (this.pointerInside) {\n last = null;\n return;\n }\n if (last === null) {\n angle = this.paintedAngle();\n } else {\n // By elapsed time rather than per frame, so the pace is the same at\n // any refresh rate. `speed` is in seconds, the timestamps in ms.\n const lap = this.speed() * 1000;\n const step = Math.min(now - last, MAX_LOOP_STEP_MS);\n if (lap > 0) angle = (angle + (360 * step) / lap) % 360;\n }\n last = now;\n host.style.setProperty('--wr-border-glow-angle', `${angle.toFixed(3)}deg`);\n };\n const start = (): void => {\n if (raf === 0) raf = requestAnimationFrame(tick);\n };\n const pause = (): void => {\n cancelAnimationFrame(raf);\n raf = 0;\n last = null;\n };\n\n // Off screen the loop stops asking for frames. Without an observer (jsdom)\n // it simply runs.\n let observer: IntersectionObserver | null = null;\n if (typeof IntersectionObserver === 'undefined') {\n start();\n } else {\n observer = new IntersectionObserver(entries => (entries[entries.length - 1].isIntersecting ? start() : pause()));\n observer.observe(host);\n }\n\n return () => {\n observer?.disconnect();\n pause();\n host.classList.remove('wr-border-glow--looping');\n };\n }\n\n /** The angle the host paints now — the pointer's, a sweep's or the loop's own. */\n private paintedAngle(): number {\n const angle = Number.parseFloat(this.el.nativeElement.style.getPropertyValue('--wr-border-glow-angle'));\n return Number.isNaN(angle) ? REST_ANGLE : angle;\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"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
|
-
import { Component, DestroyRef, ElementRef, NgZone, PLATFORM_ID, ViewEncapsulation, afterNextRender, computed, effect, inject, input, signal, untracked, viewChild } from "@angular/core";
|
|
2
|
+
import { Component, DestroyRef, ElementRef, NgZone, PLATFORM_ID, ViewEncapsulation, afterNextRender, afterRenderEffect, computed, effect, inject, input, signal, untracked, viewChild } from "@angular/core";
|
|
3
3
|
import { isPlatformBrowser } from "@angular/common";
|
|
4
4
|
import { numAttr, resolveCssColor, watchColorScheme } from "ngwr/utils";
|
|
5
5
|
import { WrPlatform } from "ngwr/platform";
|
|
@@ -80,29 +80,26 @@ var WrDotGrid = class WrDotGrid {
|
|
|
80
80
|
effect(() => {
|
|
81
81
|
this.pitch();
|
|
82
82
|
untracked(() => {
|
|
83
|
-
if (
|
|
84
|
-
this.placeDots();
|
|
85
|
-
this.requestDraw();
|
|
83
|
+
if (this.ctx) this.placeDots();
|
|
86
84
|
});
|
|
87
85
|
});
|
|
88
|
-
|
|
86
|
+
afterRenderEffect(() => {
|
|
87
|
+
if (!this.booted()) return;
|
|
88
|
+
const still = this.platform.prefersReducedMotion();
|
|
89
|
+
this.pitch();
|
|
89
90
|
this.size();
|
|
90
91
|
this.dotColor();
|
|
91
92
|
this.activeColor();
|
|
92
93
|
this.radius();
|
|
93
94
|
this.strength();
|
|
94
|
-
untracked(() =>
|
|
95
|
+
untracked(() => {
|
|
96
|
+
if (still) this.rest();
|
|
97
|
+
this.requestDraw();
|
|
98
|
+
});
|
|
95
99
|
});
|
|
96
100
|
watchColorScheme(() => this.requestDraw());
|
|
97
101
|
effect((onCleanup) => {
|
|
98
|
-
if (!this.booted()) return;
|
|
99
|
-
if (this.platform.prefersReducedMotion()) {
|
|
100
|
-
untracked(() => {
|
|
101
|
-
this.rest();
|
|
102
|
-
this.draw();
|
|
103
|
-
});
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
102
|
+
if (!this.booted() || this.platform.prefersReducedMotion()) return;
|
|
106
103
|
onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact())));
|
|
107
104
|
});
|
|
108
105
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-bits-dot-grid.mjs","names":[],"sources":["../../../projects/lib/bits/dot-grid/dot-grid.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 * After the Dot Grid background on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/dot-grid). The effect is the same\n * idea; the code is ngwr's own: a 2D canvas, one spring per dot, and a frame\n * loop that sleeps whenever nothing moves.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\n/** One dot: where the grid puts it, how far it has been pushed, and how lit it is. */\ninterface Dot {\n readonly x: number;\n readonly y: number;\n /** Offset from rest, in CSS pixels. */\n ox: number;\n oy: number;\n /** Velocity of that offset, in CSS pixels per 60 Hz frame. */\n vx: number;\n vy: number;\n /** How far toward the active colour, 0..1. */\n glow: number;\n}\n\n/**\n * The most bitmap pixels the canvas is backed with, the bound `wr-waves`\n * uses: a hero keeps the full 2x, and a very wide one steps down toward 1x\n * rather than growing a bitmap every frame has to clear and fill.\n */\nconst MAX_CANVAS_PIXELS = 2_500_000;\n\n/**\n * The smallest pitch the grid is laid out at. A 1px gap on a full-page host\n * is two million dots, which no frame can draw; 4px is already a fine mesh.\n */\nconst MIN_GAP = 4;\n\n/** A 60 Hz frame, in ms — the unit the spring constants below are tuned in. */\nconst FRAME_MS = 1000 / 60;\n\n/** The longest step one frame may take, so a tab that wakes after a pause does not fling the dots. */\nconst MAX_STEP = 3;\n\n/** Spring pull back toward the target offset, per frame. */\nconst SPRING = 0.1;\n\n/** Velocity a dot keeps from one frame to the next; under 1 is what lets it settle. */\nconst DAMPING = 0.8;\n\n/** Share of the gap to its target a dot's glow closes per frame while it brightens. */\nconst GLOW_RISE = 0.3;\n\n/** The same while it fades — slower, so the pointer leaves a short trail behind it. */\nconst GLOW_FALL = 0.06;\n\n/** Below this, in pixels or pixels per frame, a dot counts as at rest. */\nconst REST = 0.01;\n\n/** Below this a glow counts as settled: under one step of an 8-bit alpha. */\nconst GLOW_REST = 1 / 256;\n\n/** Alpha steps the lit dots are batched into, one path and one fill each. */\nconst GLOW_LEVELS = 16;\n\n/** What the dots fall back to when the colour cannot be read: `--wr-border-strong` in light. */\nconst DOT_FALLBACK = 'rgb(156, 163, 175)';\n\n/** What the lit dots fall back to: the shipped primary. */\nconst ACTIVE_FALLBACK = 'rgb(57, 105, 226)';\n\nconst TAU = Math.PI * 2;\n\n/**\n * Dot-grid background — evenly spaced dots on a 2D canvas that light up in the\n * active colour near the pointer and are pushed away from it, both by the same\n * falloff, then spring back. Fills its nearest positioned ancestor; put it\n * behind hero copy or section content.\n *\n * The pointer is followed on the host's PARENT, with passive listeners, so the\n * dots answer a pointer that is over the content in front of them rather than\n * over the canvas — the host itself takes no pointer events and never steals a\n * click. Placed directly in a shadow root, where it has no parent element, it\n * follows the pointer on the shadow host; with neither it listens nowhere and\n * the grid stays unlit. Content placed after it needs `position: relative` (or\n * any z-index) to paint above it, as with any absolutely positioned layer.\n *\n * The grid is laid out in the host's own box, before transforms, so one that\n * boots inside a scaling-in panel (a dialog opening) is not stretched when the\n * animation ends, and the pointer is mapped back through the transform.\n *\n * Decorative: the canvas is `aria-hidden`, the host has no role and nothing in\n * it takes focus. The frame loop runs only while something moves — a still\n * grid asks for no frames at all — and never while the host is off screen.\n *\n * Under `prefers-reduced-motion` one still grid is drawn, in the resting\n * colour, and nothing follows the pointer; it is drawn again when the colours\n * or the page's colour scheme change. The preference is read live.\n *\n * Colours are theme tokens by default — the dots `--wr-border-strong`, the lit\n * ones `--wr-color-primary` — through the `--wr-dot-grid-dot-color` and\n * `--wr-dot-grid-active-color` hooks, and resolved before they reach the\n * canvas, which reads neither a `var()` nor a `light-dark()`.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 24rem; overflow: hidden\">\n * <wr-dot-grid />\n * <h1 style=\"position: relative\">Hero copy above the dots</h1>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/dot-grid\n */\n@Component({\n selector: 'wr-dot-grid',\n template: '<canvas #canvas class=\"wr-dot-grid__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './dot-grid.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-dot-grid',\n // Feed the CSS stand-in grid (see `styles/_rules.scss`) so it draws at the\n // real pitch, size and colour, and switch it off once the canvas has painted.\n '[style.--wr-dot-grid-gap.px]': 'pitch()',\n '[style.--wr-dot-grid-dot-size.px]': 'size()',\n '[style.--wr-dot-grid-input-dot-color]': 'standInColor()',\n '[class.wr-dot-grid--painted]': 'painted()',\n },\n})\nexport class WrDotGrid {\n /** Dot diameter in px. @default 3 */\n readonly dotSize = input(3, { transform: numAttr(3) });\n\n /** Distance between dot centres in px, never under 4. @default 24 */\n readonly gap = input(24, { transform: numAttr(24) });\n\n /** Resting dot colour, any CSS colour. When unset, empty or blank, `--wr-dot-grid-dot-color` decides. */\n readonly dotColor = input<string | null>(null);\n\n /** Colour a dot takes near the pointer, any CSS colour. When unset, empty or blank, `--wr-dot-grid-active-color` decides. */\n readonly activeColor = input<string | null>(null);\n\n /** How far from the pointer a dot still reacts, in px. @default 120 */\n readonly radius = input(120, { transform: numAttr(120) });\n\n /**\n * How far a dot right by the pointer is pushed, in px; the push fades out\n * toward `radius`. `0` keeps the dots in place and only lights them, and a\n * negative value pulls them in. @default 12\n */\n readonly strength = input(12, { transform: numAttr(12) });\n\n /** The pitch the grid is laid out at: `gap`, floored. */\n protected readonly pitch = computed(() => Math.max(MIN_GAP, this.gap()));\n\n /** The dot diameter drawn: `dotSize`, never negative. */\n protected readonly size = computed(() => Math.max(0, this.dotSize()));\n\n /** A colour the author chose, published so the stand-in draws in it too; `null` while the hook decides. */\n protected readonly standInColor = computed(() => (this.dotColor() ?? '').trim() || null);\n\n /** The colours the canvas resolves: an input that is unset, empty or blank leaves the hook in charge. */\n private readonly dotExpression = computed(() => (this.dotColor() ?? '').trim() || 'var(--wr-dot-grid-dot-color)');\n private readonly activeExpression = computed(\n () => (this.activeColor() ?? '').trim() || 'var(--wr-dot-grid-active-color)'\n );\n\n /**\n * Flips once the canvas has drawn, which retires the CSS stand-in grid.\n *\n * A canvas cannot be prerendered, so on an SSR'd page it is blank until the\n * bundle has loaded and hydrated. The stylesheet paints the same dots at the\n * same pitch from the prerendered HTML in the meantime, and this hands over.\n */\n protected readonly painted = signal(false);\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private ctx: CanvasRenderingContext2D | null = null;\n /** The host's layout box, in CSS pixels before any transform: what the grid is laid out in. */\n private box = { width: 0, height: 0 };\n /** Bitmap pixels per CSS pixel the canvas is backed at; 0 until the first fit. */\n private ratio = 0;\n private dots: Dot[] = [];\n /** Lit dots by alpha step, reused frame to frame. */\n private readonly lit: Dot[][] = Array.from({ length: GLOW_LEVELS }, () => []);\n /** The pointer, in viewport coordinates, and whether it is over the parent at all. */\n private readonly pointer = { clientX: 0, clientY: 0, inside: false };\n private rafId = 0;\n private lastTime = -1;\n /** Schedules a frame while motion is allowed; `null` under reduced motion. */\n private wake: (() => void) | null = null;\n\n /** Flips once `boot()` has a context and a grid — everything after waits for it. */\n private readonly booted = signal(false);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\n\n // A new pitch is a new grid. It leaves the bitmap alone, so the frame it\n // asks for repaints over the old grid rather than over a blank canvas.\n effect(() => {\n this.pitch();\n untracked(() => {\n if (!this.ctx) return;\n this.placeDots();\n this.requestDraw();\n });\n });\n\n // Everything else only changes what the next frame draws. A sleeping loop\n // or a still grid has no next frame, so ask for one.\n effect(() => {\n this.size();\n this.dotColor();\n this.activeColor();\n this.radius();\n this.strength();\n untracked(() => this.requestDraw());\n });\n\n // A drawn grid keeps the colours it was drawn in, and the loop sleeps when\n // nothing moves, so a scheme switch asks for a frame to re-resolve them.\n watchColorScheme(() => this.requestDraw());\n\n // Motion or a still grid, decided live.\n effect(onCleanup => {\n if (!this.booted()) return;\n if (this.platform.prefersReducedMotion()) {\n untracked(() => {\n this.rest();\n this.draw();\n });\n return;\n }\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact())));\n });\n }\n\n // Setup\n\n private boot(): void {\n const canvas = this.canvasRef().nativeElement;\n this.ctx = canvas.getContext('2d');\n if (!this.ctx) return;\n\n // `clientWidth` and not `getBoundingClientRect()`: the rect is the box after\n // transforms, so a grid booted inside a dialog's scale-in would be laid out\n // for 96% of its box and stretched by CSS once the animation ended. The\n // observer's first entry follows with the exact, sub-pixel size.\n const hostEl = this.host.nativeElement;\n this.fit(hostEl.clientWidth, hostEl.clientHeight);\n this.placeDots();\n\n // The box is the observer's to follow, transforms excluded. A window resize\n // is for the pixel ratio, which browser zoom and a move to another screen\n // change with no box change at all.\n const onResize = (): void => this.resize(this.box.width, this.box.height);\n window.addEventListener('resize', onResize);\n const ro = new ResizeObserver(entries => {\n const rect = entries[entries.length - 1]?.contentRect;\n if (rect) this.resize(rect.width, rect.height);\n });\n ro.observe(hostEl);\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('resize', onResize);\n ro.disconnect();\n });\n this.booted.set(true);\n }\n\n /** Follow the pointer and draw on demand; returns what stops both. */\n private interact(): () => void {\n const hostEl = this.host.nativeElement;\n // The parent, not the host: content in front of the canvas receives the\n // pointer, and its events bubble to the parent, never to a sibling. Placed\n // directly in a shadow root it has none, and the shadow host is where those\n // composed events land. Never the host itself, which takes no pointer\n // events and so could never hear one.\n const root = hostEl.getRootNode();\n const target =\n hostEl.parentElement ?? (root instanceof ShadowRoot && root.host instanceof HTMLElement ? root.host : null);\n\n // Off screen the grid asks for no frames. Without an observer (jsdom) it\n // counts as visible.\n let visible = typeof IntersectionObserver === 'undefined';\n const wake = (): void => {\n if (visible && this.rafId === 0) this.rafId = requestAnimationFrame(time => this.tick(time));\n };\n const halt = (): void => {\n cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.lastTime = -1;\n };\n\n const onMove = (event: PointerEvent): void => {\n this.pointer.clientX = event.clientX;\n this.pointer.clientY = event.clientY;\n this.pointer.inside = true;\n wake();\n };\n const onLeave = (): void => {\n this.pointer.inside = false;\n wake();\n };\n target?.addEventListener('pointermove', onMove, { passive: true });\n target?.addEventListener('pointerdown', onMove, { passive: true });\n target?.addEventListener('pointerleave', onLeave, { passive: true });\n target?.addEventListener('pointercancel', onLeave, { passive: true });\n\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => {\n visible = entries[entries.length - 1].isIntersecting;\n if (visible) wake();\n else halt();\n });\n io?.observe(hostEl);\n\n this.wake = wake;\n wake();\n\n return () => {\n target?.removeEventListener('pointermove', onMove);\n target?.removeEventListener('pointerdown', onMove);\n target?.removeEventListener('pointerleave', onLeave);\n target?.removeEventListener('pointercancel', onLeave);\n io?.disconnect();\n halt();\n this.wake = null;\n this.pointer.inside = false;\n };\n }\n\n /** Draw the grid as it should look now: a frame while motion is allowed, at once under reduced motion. */\n private requestDraw(): void {\n if (!this.booted()) return;\n if (this.wake) this.wake();\n else if (this.platform.prefersReducedMotion()) this.draw();\n }\n\n /** Fit the canvas to a new box or pixel ratio and, when that cleared it, repaint at once. */\n private resize(width: number, height: number): void {\n const moved = width !== this.box.width || height !== this.box.height;\n if (!this.fit(width, height)) return;\n if (moved) this.placeDots();\n // Observer entries arrive after the frame's own callbacks and before its\n // paint, so a frame asked for here would leave this one blank — with the\n // stand-in already retired, a resize drag would flicker every frame.\n this.draw();\n // Re-placed dots are at rest and unlit, which is right unless the pointer\n // is over them; a running loop simply carries on.\n if (this.pointer.inside) this.wake?.();\n }\n\n /**\n * Back the canvas for a `width` × `height` box, in CSS pixels, at the screen's\n * pixel ratio. Returns whether anything changed: writing `canvas.width` clears\n * the bitmap even to the value it already has, so an unchanged box is left alone.\n */\n private fit(width: number, height: number): boolean {\n // Capped at 2 and by the pixel budget, never below 1 — a bitmap the size of\n // the CSS box blurs every dot on a 2x screen.\n const area = width * height;\n const budget = area > 0 ? Math.sqrt(MAX_CANVAS_PIXELS / area) : 2;\n const ratio = Math.max(1, Math.min(window.devicePixelRatio || 1, 2, budget));\n if (width === this.box.width && height === this.box.height && ratio === this.ratio) return false;\n\n this.box = { width, height };\n this.ratio = ratio;\n const canvas = this.canvasRef().nativeElement;\n canvas.width = Math.round(width * ratio);\n canvas.height = Math.round(height * ratio);\n // Resizing the bitmap resets the transform, so the scale goes on after it.\n // Every coordinate the draw uses is in CSS pixels.\n this.ctx?.setTransform(ratio, 0, 0, ratio, 0, 0);\n return true;\n }\n\n /** Lay the dots out over the box, every one at rest. */\n private placeDots(): void {\n // One dot sits on the centre of the box and the rest step out from it by\n // the pitch — exactly where the stand-in's centred `background-position`\n // puts them, so the handover does not shift a single dot. One ring past\n // each edge, for the dots a push brings into view.\n const gap = this.pitch();\n const xs = this.axis(this.box.width, gap);\n const ys = this.axis(this.box.height, gap);\n const dots: Dot[] = [];\n for (const y of ys) {\n for (const x of xs) dots.push({ x, y, ox: 0, oy: 0, vx: 0, vy: 0, glow: 0 });\n }\n this.dots = dots;\n }\n\n /** Positions along one side of `length`: the centre, then every `gap` out to one step past each edge. */\n private axis(length: number, gap: number): number[] {\n const centre = length / 2;\n const steps = Math.ceil(centre / gap) + 1;\n const positions: number[] = [];\n for (let i = -steps; i <= steps; i++) positions.push(centre + i * gap);\n return positions;\n }\n\n /** Every dot back at rest and unlit, and the pointer gone. */\n private rest(): void {\n this.pointer.inside = false;\n for (const dot of this.dots) {\n dot.ox = dot.oy = dot.vx = dot.vy = dot.glow = 0;\n }\n }\n\n // Frame loop\n\n private tick(time: number): void {\n this.rafId = 0;\n // The springs are tuned per 60 Hz frame and stepped by the time that really\n // passed, so a 120 Hz screen does not run them twice as fast. A timestamp\n // that did not advance counts as one frame.\n const elapsed = time - this.lastTime;\n const step = this.lastTime < 0 || elapsed <= 0 ? 1 : Math.min(MAX_STEP, elapsed / FRAME_MS);\n this.lastTime = time;\n\n const moving = this.move(step);\n this.draw();\n\n // Nothing left to move: sleep until the pointer, a resize or a new input\n // asks for another frame.\n if (moving && this.wake) this.rafId = requestAnimationFrame(next => this.tick(next));\n else this.lastTime = -1;\n }\n\n /** Advance every dot by `step` frames; returns whether any is still moving. */\n private move(step: number): boolean {\n const reach = Math.max(0, this.radius());\n const push = this.strength();\n const active = this.pointer.inside && reach > 0;\n\n let px = 0;\n let py = 0;\n if (active) {\n // Read once a frame rather than once a move: a page that scrolls under a\n // still pointer moves the grid under it too. The rect is the box as\n // drawn, after any transform on it or an ancestor, and the grid is laid\n // out in the box before them, so the pointer is scaled back into it.\n const rect = this.host.nativeElement.getBoundingClientRect();\n px = (this.pointer.clientX - rect.left) * (rect.width > 0 ? this.box.width / rect.width : 1);\n py = (this.pointer.clientY - rect.top) * (rect.height > 0 ? this.box.height / rect.height : 1);\n }\n\n const reach2 = reach * reach;\n const pull = SPRING * step;\n const keep = Math.pow(DAMPING, step);\n const rise = 1 - Math.pow(1 - GLOW_RISE, step);\n const fall = 1 - Math.pow(1 - GLOW_FALL, step);\n\n let moving = false;\n for (const dot of this.dots) {\n let tx = 0;\n let ty = 0;\n let glow = 0;\n if (active) {\n const dx = dot.x - px;\n const dy = dot.y - py;\n const d2 = dx * dx + dy * dy;\n if (d2 < reach2) {\n const d = Math.sqrt(d2);\n const u = 1 - d / reach;\n // Smoothstep: no edge where the radius ends, and a flat top under the pointer.\n glow = u * u * (3 - 2 * u);\n if (d > 0) {\n tx = (dx / d) * push * glow;\n ty = (dy / d) * push * glow;\n }\n }\n }\n\n dot.vx = (dot.vx + (tx - dot.ox) * pull) * keep;\n dot.vy = (dot.vy + (ty - dot.oy) * pull) * keep;\n dot.ox += dot.vx * step;\n dot.oy += dot.vy * step;\n dot.glow += (glow - dot.glow) * (glow > dot.glow ? rise : fall);\n\n if (\n !moving &&\n (Math.abs(tx - dot.ox) > REST ||\n Math.abs(ty - dot.oy) > REST ||\n Math.abs(dot.vx) > REST ||\n Math.abs(dot.vy) > REST ||\n Math.abs(glow - dot.glow) > GLOW_REST)\n ) {\n moving = true;\n }\n }\n return moving;\n }\n\n private draw(): void {\n const ctx = this.ctx;\n if (!ctx) return;\n // Retire the stand-in only once something is really painted. Runs outside\n // Angular, so the `set` schedules the one host-binding update it needs.\n if (!this.painted()) this.painted.set(true);\n\n const canvas = this.canvasRef().nativeElement;\n const r = this.size() / 2;\n ctx.clearRect(0, 0, this.box.width, this.box.height);\n ctx.globalAlpha = 1;\n ctx.fillStyle = resolveCssColor(canvas, this.dotExpression(), DOT_FALLBACK);\n this.fillDots(ctx, this.dots, r);\n\n // Lit dots go over their resting selves in the active colour, at their\n // glow as alpha: the blend of the two colours without parsing either, so\n // an `oklch()` or a `display-p3` colour works as well as an `rgb()`.\n for (const level of this.lit) level.length = 0;\n let anyLit = false;\n for (const dot of this.dots) {\n const level = Math.round(dot.glow * GLOW_LEVELS);\n if (level > 0) {\n this.lit[Math.min(level, GLOW_LEVELS) - 1].push(dot);\n anyLit = true;\n }\n }\n if (!anyLit) return;\n\n ctx.fillStyle = resolveCssColor(canvas, this.activeExpression(), ACTIVE_FALLBACK);\n this.lit.forEach((dots, i) => {\n if (dots.length === 0) return;\n ctx.globalAlpha = (i + 1) / GLOW_LEVELS;\n this.fillDots(ctx, dots, r);\n });\n ctx.globalAlpha = 1;\n }\n\n /** One path for every dot in `dots`, filled once. */\n private fillDots(ctx: CanvasRenderingContext2D, dots: readonly Dot[], r: number): void {\n ctx.beginPath();\n for (const dot of dots) {\n const x = dot.x + dot.ox;\n const y = dot.y + dot.oy;\n ctx.moveTo(x + r, y);\n ctx.arc(x, y, r, 0, TAU);\n }\n ctx.fill();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-bits-dot-grid.mjs","names":[],"sources":["../../../projects/lib/bits/dot-grid/dot-grid.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 * After the Dot Grid background on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/dot-grid). The effect is the same\n * idea; the code is ngwr's own: a 2D canvas, one spring per dot, and a frame\n * loop that sleeps whenever nothing moves.\n */\n\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n afterRenderEffect,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\n/** One dot: where the grid puts it, how far it has been pushed, and how lit it is. */\ninterface Dot {\n readonly x: number;\n readonly y: number;\n /** Offset from rest, in CSS pixels. */\n ox: number;\n oy: number;\n /** Velocity of that offset, in CSS pixels per 60 Hz frame. */\n vx: number;\n vy: number;\n /** How far toward the active colour, 0..1. */\n glow: number;\n}\n\n/**\n * The most bitmap pixels the canvas is backed with, the bound `wr-waves`\n * uses: a hero keeps the full 2x, and a very wide one steps down toward 1x\n * rather than growing a bitmap every frame has to clear and fill.\n */\nconst MAX_CANVAS_PIXELS = 2_500_000;\n\n/**\n * The smallest pitch the grid is laid out at. A 1px gap on a full-page host\n * is two million dots, which no frame can draw; 4px is already a fine mesh.\n */\nconst MIN_GAP = 4;\n\n/** A 60 Hz frame, in ms — the unit the spring constants below are tuned in. */\nconst FRAME_MS = 1000 / 60;\n\n/** The longest step one frame may take, so a tab that wakes after a pause does not fling the dots. */\nconst MAX_STEP = 3;\n\n/** Spring pull back toward the target offset, per frame. */\nconst SPRING = 0.1;\n\n/** Velocity a dot keeps from one frame to the next; under 1 is what lets it settle. */\nconst DAMPING = 0.8;\n\n/** Share of the gap to its target a dot's glow closes per frame while it brightens. */\nconst GLOW_RISE = 0.3;\n\n/** The same while it fades — slower, so the pointer leaves a short trail behind it. */\nconst GLOW_FALL = 0.06;\n\n/** Below this, in pixels or pixels per frame, a dot counts as at rest. */\nconst REST = 0.01;\n\n/** Below this a glow counts as settled: under one step of an 8-bit alpha. */\nconst GLOW_REST = 1 / 256;\n\n/** Alpha steps the lit dots are batched into, one path and one fill each. */\nconst GLOW_LEVELS = 16;\n\n/** What the dots fall back to when the colour cannot be read: `--wr-border-strong` in light. */\nconst DOT_FALLBACK = 'rgb(156, 163, 175)';\n\n/** What the lit dots fall back to: the shipped primary. */\nconst ACTIVE_FALLBACK = 'rgb(57, 105, 226)';\n\nconst TAU = Math.PI * 2;\n\n/**\n * Dot-grid background — evenly spaced dots on a 2D canvas that light up in the\n * active colour near the pointer and are pushed away from it, both by the same\n * falloff, then spring back. Fills its nearest positioned ancestor; put it\n * behind hero copy or section content.\n *\n * The pointer is followed on the host's PARENT, with passive listeners, so the\n * dots answer a pointer that is over the content in front of them rather than\n * over the canvas — the host itself takes no pointer events and never steals a\n * click. Placed directly in a shadow root, where it has no parent element, it\n * follows the pointer on the shadow host; with neither it listens nowhere and\n * the grid stays unlit. Content placed after it needs `position: relative` (or\n * any z-index) to paint above it, as with any absolutely positioned layer.\n *\n * The grid is laid out in the host's own box, before transforms, so one that\n * boots inside a scaling-in panel (a dialog opening) is not stretched when the\n * animation ends, and the pointer is mapped back through the transform.\n *\n * Decorative: the canvas is `aria-hidden`, the host has no role and nothing in\n * it takes focus. The frame loop runs only while something moves — a still\n * grid asks for no frames at all — and never while the host is off screen.\n *\n * Under `prefers-reduced-motion` one still grid is drawn, in the resting\n * colour, and nothing follows the pointer; it is drawn again when the colours\n * or the page's colour scheme change. The preference is read live.\n *\n * Colours are theme tokens by default — the dots `--wr-border-strong`, the lit\n * ones `--wr-color-primary` — through the `--wr-dot-grid-dot-color` and\n * `--wr-dot-grid-active-color` hooks, and resolved before they reach the\n * canvas, which reads neither a `var()` nor a `light-dark()`.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 24rem; overflow: hidden\">\n * <wr-dot-grid />\n * <h1 style=\"position: relative\">Hero copy above the dots</h1>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/dot-grid\n */\n@Component({\n selector: 'wr-dot-grid',\n template: '<canvas #canvas class=\"wr-dot-grid__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './dot-grid.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-dot-grid',\n // Feed the CSS stand-in grid (see `styles/_rules.scss`) so it draws at the\n // real pitch, size and colour, and switch it off once the canvas has painted.\n '[style.--wr-dot-grid-gap.px]': 'pitch()',\n '[style.--wr-dot-grid-dot-size.px]': 'size()',\n '[style.--wr-dot-grid-input-dot-color]': 'standInColor()',\n '[class.wr-dot-grid--painted]': 'painted()',\n },\n})\nexport class WrDotGrid {\n /** Dot diameter in px. @default 3 */\n readonly dotSize = input(3, { transform: numAttr(3) });\n\n /** Distance between dot centres in px, never under 4. @default 24 */\n readonly gap = input(24, { transform: numAttr(24) });\n\n /** Resting dot colour, any CSS colour. When unset, empty or blank, `--wr-dot-grid-dot-color` decides. */\n readonly dotColor = input<string | null>(null);\n\n /** Colour a dot takes near the pointer, any CSS colour. When unset, empty or blank, `--wr-dot-grid-active-color` decides. */\n readonly activeColor = input<string | null>(null);\n\n /** How far from the pointer a dot still reacts, in px. @default 120 */\n readonly radius = input(120, { transform: numAttr(120) });\n\n /**\n * How far a dot right by the pointer is pushed, in px; the push fades out\n * toward `radius`. `0` keeps the dots in place and only lights them, and a\n * negative value pulls them in. @default 12\n */\n readonly strength = input(12, { transform: numAttr(12) });\n\n /** The pitch the grid is laid out at: `gap`, floored. */\n protected readonly pitch = computed(() => Math.max(MIN_GAP, this.gap()));\n\n /** The dot diameter drawn: `dotSize`, never negative. */\n protected readonly size = computed(() => Math.max(0, this.dotSize()));\n\n /** A colour the author chose, published so the stand-in draws in it too; `null` while the hook decides. */\n protected readonly standInColor = computed(() => (this.dotColor() ?? '').trim() || null);\n\n /** The colours the canvas resolves: an input that is unset, empty or blank leaves the hook in charge. */\n private readonly dotExpression = computed(() => (this.dotColor() ?? '').trim() || 'var(--wr-dot-grid-dot-color)');\n private readonly activeExpression = computed(\n () => (this.activeColor() ?? '').trim() || 'var(--wr-dot-grid-active-color)'\n );\n\n /**\n * Flips once the canvas has drawn, which retires the CSS stand-in grid.\n *\n * A canvas cannot be prerendered, so on an SSR'd page it is blank until the\n * bundle has loaded and hydrated. The stylesheet paints the same dots at the\n * same pitch from the prerendered HTML in the meantime, and this hands over.\n */\n protected readonly painted = signal(false);\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private ctx: CanvasRenderingContext2D | null = null;\n /** The host's layout box, in CSS pixels before any transform: what the grid is laid out in. */\n private box = { width: 0, height: 0 };\n /** Bitmap pixels per CSS pixel the canvas is backed at; 0 until the first fit. */\n private ratio = 0;\n private dots: Dot[] = [];\n /** Lit dots by alpha step, reused frame to frame. */\n private readonly lit: Dot[][] = Array.from({ length: GLOW_LEVELS }, () => []);\n /** The pointer, in viewport coordinates, and whether it is over the parent at all. */\n private readonly pointer = { clientX: 0, clientY: 0, inside: false };\n private rafId = 0;\n private lastTime = -1;\n /** Schedules a frame while motion is allowed; `null` under reduced motion. */\n private wake: (() => void) | null = null;\n\n /** Flips once `boot()` has a context and a grid — everything after waits for it. */\n private readonly booted = signal(false);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\n\n // A new pitch is a new grid. It leaves the bitmap alone, so the frame asked\n // for below repaints over the old grid rather than over a blank canvas.\n effect(() => {\n this.pitch();\n untracked(() => {\n if (this.ctx) this.placeDots();\n });\n });\n\n // Everything that changes the picture only changes what the next frame\n // draws. A sleeping loop has no next frame, so ask for one; a still grid has\n // none either, so it is drawn at once, every dot at rest and unlit — and so\n // it is the moment motion stops. After render rather than an `effect`, which\n // runs before a directive's host binding: a still grid drawn then would miss\n // a hook it changes in the same pass.\n afterRenderEffect(() => {\n if (!this.booted()) return;\n const still = this.platform.prefersReducedMotion();\n this.pitch();\n this.size();\n this.dotColor();\n this.activeColor();\n this.radius();\n this.strength();\n untracked(() => {\n if (still) this.rest();\n this.requestDraw();\n });\n });\n\n // A drawn grid keeps the colours it was drawn in, and the loop sleeps when\n // nothing moves, so a scheme switch asks for a frame to re-resolve them.\n watchColorScheme(() => this.requestDraw());\n\n // Motion, decided live: never under `prefers-reduced-motion`, which the\n // still grid above answers.\n effect(onCleanup => {\n if (!this.booted() || this.platform.prefersReducedMotion()) return;\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.interact())));\n });\n }\n\n // Setup\n\n private boot(): void {\n const canvas = this.canvasRef().nativeElement;\n this.ctx = canvas.getContext('2d');\n if (!this.ctx) return;\n\n // `clientWidth` and not `getBoundingClientRect()`: the rect is the box after\n // transforms, so a grid booted inside a dialog's scale-in would be laid out\n // for 96% of its box and stretched by CSS once the animation ended. The\n // observer's first entry follows with the exact, sub-pixel size.\n const hostEl = this.host.nativeElement;\n this.fit(hostEl.clientWidth, hostEl.clientHeight);\n this.placeDots();\n\n // The box is the observer's to follow, transforms excluded. A window resize\n // is for the pixel ratio, which browser zoom and a move to another screen\n // change with no box change at all.\n const onResize = (): void => this.resize(this.box.width, this.box.height);\n window.addEventListener('resize', onResize);\n const ro = new ResizeObserver(entries => {\n const rect = entries[entries.length - 1]?.contentRect;\n if (rect) this.resize(rect.width, rect.height);\n });\n ro.observe(hostEl);\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('resize', onResize);\n ro.disconnect();\n });\n this.booted.set(true);\n }\n\n /** Follow the pointer and draw on demand; returns what stops both. */\n private interact(): () => void {\n const hostEl = this.host.nativeElement;\n // The parent, not the host: content in front of the canvas receives the\n // pointer, and its events bubble to the parent, never to a sibling. Placed\n // directly in a shadow root it has none, and the shadow host is where those\n // composed events land. Never the host itself, which takes no pointer\n // events and so could never hear one.\n const root = hostEl.getRootNode();\n const target =\n hostEl.parentElement ?? (root instanceof ShadowRoot && root.host instanceof HTMLElement ? root.host : null);\n\n // Off screen the grid asks for no frames. Without an observer (jsdom) it\n // counts as visible.\n let visible = typeof IntersectionObserver === 'undefined';\n const wake = (): void => {\n if (visible && this.rafId === 0) this.rafId = requestAnimationFrame(time => this.tick(time));\n };\n const halt = (): void => {\n cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n this.lastTime = -1;\n };\n\n const onMove = (event: PointerEvent): void => {\n this.pointer.clientX = event.clientX;\n this.pointer.clientY = event.clientY;\n this.pointer.inside = true;\n wake();\n };\n const onLeave = (): void => {\n this.pointer.inside = false;\n wake();\n };\n target?.addEventListener('pointermove', onMove, { passive: true });\n target?.addEventListener('pointerdown', onMove, { passive: true });\n target?.addEventListener('pointerleave', onLeave, { passive: true });\n target?.addEventListener('pointercancel', onLeave, { passive: true });\n\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => {\n visible = entries[entries.length - 1].isIntersecting;\n if (visible) wake();\n else halt();\n });\n io?.observe(hostEl);\n\n this.wake = wake;\n wake();\n\n return () => {\n target?.removeEventListener('pointermove', onMove);\n target?.removeEventListener('pointerdown', onMove);\n target?.removeEventListener('pointerleave', onLeave);\n target?.removeEventListener('pointercancel', onLeave);\n io?.disconnect();\n halt();\n this.wake = null;\n this.pointer.inside = false;\n };\n }\n\n /** Draw the grid as it should look now: a frame while motion is allowed, at once under reduced motion. */\n private requestDraw(): void {\n if (!this.booted()) return;\n if (this.wake) this.wake();\n else if (this.platform.prefersReducedMotion()) this.draw();\n }\n\n /** Fit the canvas to a new box or pixel ratio and, when that cleared it, repaint at once. */\n private resize(width: number, height: number): void {\n const moved = width !== this.box.width || height !== this.box.height;\n if (!this.fit(width, height)) return;\n if (moved) this.placeDots();\n // Observer entries arrive after the frame's own callbacks and before its\n // paint, so a frame asked for here would leave this one blank — with the\n // stand-in already retired, a resize drag would flicker every frame.\n this.draw();\n // Re-placed dots are at rest and unlit, which is right unless the pointer\n // is over them; a running loop simply carries on.\n if (this.pointer.inside) this.wake?.();\n }\n\n /**\n * Back the canvas for a `width` × `height` box, in CSS pixels, at the screen's\n * pixel ratio. Returns whether anything changed: writing `canvas.width` clears\n * the bitmap even to the value it already has, so an unchanged box is left alone.\n */\n private fit(width: number, height: number): boolean {\n // Capped at 2 and by the pixel budget, never below 1 — a bitmap the size of\n // the CSS box blurs every dot on a 2x screen.\n const area = width * height;\n const budget = area > 0 ? Math.sqrt(MAX_CANVAS_PIXELS / area) : 2;\n const ratio = Math.max(1, Math.min(window.devicePixelRatio || 1, 2, budget));\n if (width === this.box.width && height === this.box.height && ratio === this.ratio) return false;\n\n this.box = { width, height };\n this.ratio = ratio;\n const canvas = this.canvasRef().nativeElement;\n canvas.width = Math.round(width * ratio);\n canvas.height = Math.round(height * ratio);\n // Resizing the bitmap resets the transform, so the scale goes on after it.\n // Every coordinate the draw uses is in CSS pixels.\n this.ctx?.setTransform(ratio, 0, 0, ratio, 0, 0);\n return true;\n }\n\n /** Lay the dots out over the box, every one at rest. */\n private placeDots(): void {\n // One dot sits on the centre of the box and the rest step out from it by\n // the pitch — exactly where the stand-in's centred `background-position`\n // puts them, so the handover does not shift a single dot. One ring past\n // each edge, for the dots a push brings into view.\n const gap = this.pitch();\n const xs = this.axis(this.box.width, gap);\n const ys = this.axis(this.box.height, gap);\n const dots: Dot[] = [];\n for (const y of ys) {\n for (const x of xs) dots.push({ x, y, ox: 0, oy: 0, vx: 0, vy: 0, glow: 0 });\n }\n this.dots = dots;\n }\n\n /** Positions along one side of `length`: the centre, then every `gap` out to one step past each edge. */\n private axis(length: number, gap: number): number[] {\n const centre = length / 2;\n const steps = Math.ceil(centre / gap) + 1;\n const positions: number[] = [];\n for (let i = -steps; i <= steps; i++) positions.push(centre + i * gap);\n return positions;\n }\n\n /** Every dot back at rest and unlit, and the pointer gone. */\n private rest(): void {\n this.pointer.inside = false;\n for (const dot of this.dots) {\n dot.ox = dot.oy = dot.vx = dot.vy = dot.glow = 0;\n }\n }\n\n // Frame loop\n\n private tick(time: number): void {\n this.rafId = 0;\n // The springs are tuned per 60 Hz frame and stepped by the time that really\n // passed, so a 120 Hz screen does not run them twice as fast. A timestamp\n // that did not advance counts as one frame.\n const elapsed = time - this.lastTime;\n const step = this.lastTime < 0 || elapsed <= 0 ? 1 : Math.min(MAX_STEP, elapsed / FRAME_MS);\n this.lastTime = time;\n\n const moving = this.move(step);\n this.draw();\n\n // Nothing left to move: sleep until the pointer, a resize or a new input\n // asks for another frame.\n if (moving && this.wake) this.rafId = requestAnimationFrame(next => this.tick(next));\n else this.lastTime = -1;\n }\n\n /** Advance every dot by `step` frames; returns whether any is still moving. */\n private move(step: number): boolean {\n const reach = Math.max(0, this.radius());\n const push = this.strength();\n const active = this.pointer.inside && reach > 0;\n\n let px = 0;\n let py = 0;\n if (active) {\n // Read once a frame rather than once a move: a page that scrolls under a\n // still pointer moves the grid under it too. The rect is the box as\n // drawn, after any transform on it or an ancestor, and the grid is laid\n // out in the box before them, so the pointer is scaled back into it.\n const rect = this.host.nativeElement.getBoundingClientRect();\n px = (this.pointer.clientX - rect.left) * (rect.width > 0 ? this.box.width / rect.width : 1);\n py = (this.pointer.clientY - rect.top) * (rect.height > 0 ? this.box.height / rect.height : 1);\n }\n\n const reach2 = reach * reach;\n const pull = SPRING * step;\n const keep = Math.pow(DAMPING, step);\n const rise = 1 - Math.pow(1 - GLOW_RISE, step);\n const fall = 1 - Math.pow(1 - GLOW_FALL, step);\n\n let moving = false;\n for (const dot of this.dots) {\n let tx = 0;\n let ty = 0;\n let glow = 0;\n if (active) {\n const dx = dot.x - px;\n const dy = dot.y - py;\n const d2 = dx * dx + dy * dy;\n if (d2 < reach2) {\n const d = Math.sqrt(d2);\n const u = 1 - d / reach;\n // Smoothstep: no edge where the radius ends, and a flat top under the pointer.\n glow = u * u * (3 - 2 * u);\n if (d > 0) {\n tx = (dx / d) * push * glow;\n ty = (dy / d) * push * glow;\n }\n }\n }\n\n dot.vx = (dot.vx + (tx - dot.ox) * pull) * keep;\n dot.vy = (dot.vy + (ty - dot.oy) * pull) * keep;\n dot.ox += dot.vx * step;\n dot.oy += dot.vy * step;\n dot.glow += (glow - dot.glow) * (glow > dot.glow ? rise : fall);\n\n if (\n !moving &&\n (Math.abs(tx - dot.ox) > REST ||\n Math.abs(ty - dot.oy) > REST ||\n Math.abs(dot.vx) > REST ||\n Math.abs(dot.vy) > REST ||\n Math.abs(glow - dot.glow) > GLOW_REST)\n ) {\n moving = true;\n }\n }\n return moving;\n }\n\n private draw(): void {\n const ctx = this.ctx;\n if (!ctx) return;\n // Retire the stand-in only once something is really painted. Runs outside\n // Angular, so the `set` schedules the one host-binding update it needs.\n if (!this.painted()) this.painted.set(true);\n\n const canvas = this.canvasRef().nativeElement;\n const r = this.size() / 2;\n ctx.clearRect(0, 0, this.box.width, this.box.height);\n ctx.globalAlpha = 1;\n ctx.fillStyle = resolveCssColor(canvas, this.dotExpression(), DOT_FALLBACK);\n this.fillDots(ctx, this.dots, r);\n\n // Lit dots go over their resting selves in the active colour, at their\n // glow as alpha: the blend of the two colours without parsing either, so\n // an `oklch()` or a `display-p3` colour works as well as an `rgb()`.\n for (const level of this.lit) level.length = 0;\n let anyLit = false;\n for (const dot of this.dots) {\n const level = Math.round(dot.glow * GLOW_LEVELS);\n if (level > 0) {\n this.lit[Math.min(level, GLOW_LEVELS) - 1].push(dot);\n anyLit = true;\n }\n }\n if (!anyLit) return;\n\n ctx.fillStyle = resolveCssColor(canvas, this.activeExpression(), ACTIVE_FALLBACK);\n this.lit.forEach((dots, i) => {\n if (dots.length === 0) return;\n ctx.globalAlpha = (i + 1) / GLOW_LEVELS;\n this.fillDots(ctx, dots, r);\n });\n ctx.globalAlpha = 1;\n }\n\n /** One path for every dot in `dots`, filled once. */\n private fillDots(ctx: CanvasRenderingContext2D, dots: readonly Dot[], r: number): void {\n ctx.beginPath();\n for (const dot of dots) {\n const x = dot.x + dot.ox;\n const y = dot.y + dot.oy;\n ctx.moveTo(x + r, y);\n ctx.arc(x, y, r, 0, TAU);\n }\n ctx.fill();\n }\n}\n"],"mappings":"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"}
|
|
@@ -310,16 +310,29 @@ var WrFuzzyText = class WrFuzzyText {
|
|
|
310
310
|
}
|
|
311
311
|
rafId = requestAnimationFrame(run);
|
|
312
312
|
};
|
|
313
|
+
let visible = typeof IntersectionObserver === "undefined";
|
|
314
|
+
let hasBox = canvas.clientWidth > 0 && canvas.clientHeight > 0;
|
|
313
315
|
const resume = () => {
|
|
314
|
-
if (rafId === 0) rafId = requestAnimationFrame(run);
|
|
316
|
+
if (visible && hasBox && rafId === 0) rafId = requestAnimationFrame(run);
|
|
315
317
|
};
|
|
316
318
|
const park = () => {
|
|
317
319
|
cancelAnimationFrame(rafId);
|
|
318
320
|
rafId = 0;
|
|
319
321
|
};
|
|
320
|
-
const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) =>
|
|
321
|
-
|
|
322
|
-
|
|
322
|
+
const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) => {
|
|
323
|
+
const { isIntersecting, intersectionRatio } = entries[entries.length - 1];
|
|
324
|
+
visible = isIntersecting && intersectionRatio > 0;
|
|
325
|
+
if (visible) resume();
|
|
326
|
+
else park();
|
|
327
|
+
}, { threshold: [0, 1e-9] });
|
|
328
|
+
io?.observe(canvas);
|
|
329
|
+
const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => {
|
|
330
|
+
hasBox = canvas.clientWidth > 0 && canvas.clientHeight > 0;
|
|
331
|
+
if (hasBox) resume();
|
|
332
|
+
else park();
|
|
333
|
+
});
|
|
334
|
+
ro?.observe(canvas);
|
|
335
|
+
resume();
|
|
323
336
|
const isInsideText = (x, y) => x >= interactiveLeft && x <= interactiveRight && y >= interactiveTop && y <= interactiveBottom;
|
|
324
337
|
const isOverText = (clientX, clientY) => {
|
|
325
338
|
const rect = canvas.getBoundingClientRect();
|
|
@@ -357,6 +370,7 @@ var WrFuzzyText = class WrFuzzyText {
|
|
|
357
370
|
this.teardown = () => {
|
|
358
371
|
park();
|
|
359
372
|
io?.disconnect();
|
|
373
|
+
ro?.disconnect();
|
|
360
374
|
window.removeEventListener("resize", onResize);
|
|
361
375
|
clearTimeout(glitchOnTimer);
|
|
362
376
|
clearTimeout(glitchOffTimer);
|