ngwr 15.1.0 → 15.2.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/aurora/styles/_rules.scss +11 -0
- package/bits/dot-grid/styles/_rules.scss +9 -1
- package/bits/iridescence/iridescence.scss +1 -0
- package/bits/iridescence/styles/_index.scss +8 -0
- package/bits/iridescence/styles/_rules.scss +72 -0
- package/bits/marquee/styles/_rules.scss +7 -3
- package/bits/ripple-grid/styles/_rules.scss +10 -2
- package/bits/spotlight-card/styles/_rules.scss +7 -4
- package/bits/star-border/styles/_rules.scss +5 -2
- package/bits/threads/styles/_rules.scss +9 -1
- package/bits/waves/styles/_rules.scss +8 -1
- package/charts/candlestick-chart/styles/_index.scss +2 -2
- package/fesm2022/ngwr-affix.mjs +3 -3
- package/fesm2022/ngwr-alert.mjs +3 -3
- package/fesm2022/ngwr-anchor.mjs +3 -3
- package/fesm2022/ngwr-avatar.mjs +3 -3
- package/fesm2022/ngwr-back-top.mjs +3 -3
- package/fesm2022/ngwr-badge.mjs +6 -6
- package/fesm2022/ngwr-bits-aurora.mjs +30 -38
- package/fesm2022/ngwr-bits-aurora.mjs.map +1 -1
- package/fesm2022/ngwr-bits-blur-text.mjs +3 -3
- package/fesm2022/ngwr-bits-border-glow-testing.mjs.map +1 -1
- package/fesm2022/ngwr-bits-border-glow.mjs +11 -5
- package/fesm2022/ngwr-bits-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-bits-circular-text.mjs +3 -3
- package/fesm2022/ngwr-bits-click-spark.mjs +3 -3
- package/fesm2022/ngwr-bits-confetti.mjs +3 -3
- package/fesm2022/ngwr-bits-decrypt-text.mjs +3 -3
- package/fesm2022/ngwr-bits-dot-grid.mjs +12 -9
- package/fesm2022/ngwr-bits-dot-grid.mjs.map +1 -1
- package/fesm2022/ngwr-bits-falling-text.mjs +3 -3
- package/fesm2022/ngwr-bits-fuzzy-text.mjs +3 -3
- package/fesm2022/ngwr-bits-glitch-text.mjs +3 -3
- package/fesm2022/ngwr-bits-gradient-text.mjs +3 -3
- package/fesm2022/ngwr-bits-iridescence-testing.mjs +31 -0
- package/fesm2022/ngwr-bits-iridescence-testing.mjs.map +1 -0
- package/fesm2022/ngwr-bits-iridescence.mjs +591 -0
- package/fesm2022/ngwr-bits-iridescence.mjs.map +1 -0
- package/fesm2022/ngwr-bits-marquee-testing.mjs +1 -1
- package/fesm2022/ngwr-bits-marquee-testing.mjs.map +1 -1
- package/fesm2022/ngwr-bits-marquee.mjs +7 -7
- package/fesm2022/ngwr-bits-marquee.mjs.map +1 -1
- package/fesm2022/ngwr-bits-ripple-grid.mjs +8 -11
- package/fesm2022/ngwr-bits-ripple-grid.mjs.map +1 -1
- package/fesm2022/ngwr-bits-rotating-text.mjs +3 -3
- package/fesm2022/ngwr-bits-shiny-text.mjs +6 -6
- package/fesm2022/ngwr-bits-splash-cursor.mjs +3 -3
- package/fesm2022/ngwr-bits-split-text.mjs +3 -3
- package/fesm2022/ngwr-bits-spotlight-card-testing.mjs +1 -1
- package/fesm2022/ngwr-bits-spotlight-card-testing.mjs.map +1 -1
- package/fesm2022/ngwr-bits-spotlight-card.mjs +10 -10
- package/fesm2022/ngwr-bits-spotlight-card.mjs.map +1 -1
- package/fesm2022/ngwr-bits-star-border-testing.mjs +1 -1
- package/fesm2022/ngwr-bits-star-border-testing.mjs.map +1 -1
- package/fesm2022/ngwr-bits-star-border.mjs +7 -7
- package/fesm2022/ngwr-bits-star-border.mjs.map +1 -1
- package/fesm2022/ngwr-bits-threads.mjs +17 -12
- package/fesm2022/ngwr-bits-threads.mjs.map +1 -1
- package/fesm2022/ngwr-bits-tilt-card.mjs +6 -6
- package/fesm2022/ngwr-bits-typewriter.mjs +3 -3
- package/fesm2022/ngwr-bits-waves.mjs +15 -11
- package/fesm2022/ngwr-bits-waves.mjs.map +1 -1
- package/fesm2022/ngwr-breadcrumbs.mjs +6 -6
- package/fesm2022/ngwr-burger.mjs +3 -3
- package/fesm2022/ngwr-button.mjs +6 -6
- package/fesm2022/ngwr-calendar.mjs +3 -3
- package/fesm2022/ngwr-card.mjs +9 -9
- package/fesm2022/ngwr-carousel.mjs +6 -6
- package/fesm2022/ngwr-cascader.mjs +3 -3
- package/fesm2022/ngwr-charts-bar-chart.mjs +3 -3
- package/fesm2022/ngwr-charts-boxplot-chart.mjs +3 -3
- package/fesm2022/ngwr-charts-bullet-chart.mjs +3 -3
- package/fesm2022/ngwr-charts-calendar-heatmap.mjs +3 -3
- package/fesm2022/ngwr-charts-candlestick-chart.mjs +7 -7
- package/fesm2022/ngwr-charts-candlestick-chart.mjs.map +1 -1
- package/fesm2022/ngwr-charts-chart-legend.mjs +6 -6
- package/fesm2022/ngwr-charts-core.mjs +3 -3
- package/fesm2022/ngwr-charts-donut-chart.mjs +3 -3
- package/fesm2022/ngwr-charts-funnel-chart.mjs +3 -3
- package/fesm2022/ngwr-charts-gauge.mjs +3 -3
- package/fesm2022/ngwr-charts-heatmap-chart.mjs +3 -3
- package/fesm2022/ngwr-charts-line-chart.mjs +3 -3
- package/fesm2022/ngwr-charts-meter-group.mjs +3 -3
- package/fesm2022/ngwr-charts-radar-chart.mjs +3 -3
- package/fesm2022/ngwr-charts-scatter-chart.mjs +3 -3
- package/fesm2022/ngwr-charts-sparkline.mjs +3 -3
- package/fesm2022/ngwr-charts-treemap-chart.mjs +3 -3
- package/fesm2022/ngwr-charts-waterfall-chart.mjs +3 -3
- package/fesm2022/ngwr-checkbox.mjs +6 -6
- package/fesm2022/ngwr-clipboard.mjs +3 -3
- package/fesm2022/ngwr-collapse.mjs +9 -9
- package/fesm2022/ngwr-color-picker.mjs +6 -6
- package/fesm2022/ngwr-command-palette.mjs +3 -3
- package/fesm2022/ngwr-compare.mjs +3 -3
- package/fesm2022/ngwr-context-menu.mjs +12 -12
- package/fesm2022/ngwr-cookie.mjs +3 -3
- package/fesm2022/ngwr-counter.mjs +6 -6
- package/fesm2022/ngwr-date-picker.mjs +21 -21
- package/fesm2022/ngwr-date.mjs +6 -6
- package/fesm2022/ngwr-density.mjs +6 -6
- package/fesm2022/ngwr-descriptions.mjs +6 -6
- package/fesm2022/ngwr-dialog.mjs +15 -15
- package/fesm2022/ngwr-directives.mjs +12 -12
- package/fesm2022/ngwr-divider.mjs +3 -3
- package/fesm2022/ngwr-drawer.mjs +18 -18
- package/fesm2022/ngwr-dropdown.mjs +9 -9
- package/fesm2022/ngwr-editor.mjs +3 -3
- package/fesm2022/ngwr-empty.mjs +3 -3
- package/fesm2022/ngwr-event-calendar.mjs +6 -6
- package/fesm2022/ngwr-file-upload.mjs +3 -3
- package/fesm2022/ngwr-form.mjs +9 -9
- package/fesm2022/ngwr-graph.mjs +6 -6
- package/fesm2022/ngwr-hotkey.mjs +6 -6
- package/fesm2022/ngwr-i18n.mjs +9 -9
- package/fesm2022/ngwr-icon.mjs +6 -6
- package/fesm2022/ngwr-image-cropper.mjs +3 -3
- package/fesm2022/ngwr-input-number.mjs +3 -3
- package/fesm2022/ngwr-input-otp.mjs +3 -3
- package/fesm2022/ngwr-input.mjs +15 -15
- package/fesm2022/ngwr-keyboard.mjs +3 -3
- package/fesm2022/ngwr-knob.mjs +3 -3
- package/fesm2022/ngwr-layout.mjs +15 -15
- package/fesm2022/ngwr-lightbox.mjs +3 -3
- package/fesm2022/ngwr-list.mjs +6 -6
- package/fesm2022/ngwr-loading-bar.mjs +6 -6
- package/fesm2022/ngwr-mention.mjs +6 -6
- package/fesm2022/ngwr-meta.mjs +6 -6
- package/fesm2022/ngwr-overlay.mjs +6 -6
- package/fesm2022/ngwr-page-header.mjs +3 -3
- package/fesm2022/ngwr-pagination.mjs +3 -3
- package/fesm2022/ngwr-pipes.mjs +21 -21
- package/fesm2022/ngwr-platform.mjs +6 -6
- package/fesm2022/ngwr-popconfirm.mjs +6 -6
- package/fesm2022/ngwr-popover.mjs +12 -12
- package/fesm2022/ngwr-progress.mjs +3 -3
- package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
- package/fesm2022/ngwr-qr.mjs +4 -3
- package/fesm2022/ngwr-qr.mjs.map +1 -1
- package/fesm2022/ngwr-radio.mjs +6 -6
- package/fesm2022/ngwr-rating.mjs +3 -3
- package/fesm2022/ngwr-result.mjs +12 -12
- package/fesm2022/ngwr-schema-form.mjs +3 -3
- package/fesm2022/ngwr-scroll.mjs +3 -3
- package/fesm2022/ngwr-segmented.mjs +3 -3
- package/fesm2022/ngwr-select.mjs +12 -12
- package/fesm2022/ngwr-sidebar.mjs +3 -3
- package/fesm2022/ngwr-skeleton.mjs +3 -3
- package/fesm2022/ngwr-slider.mjs +3 -3
- package/fesm2022/ngwr-sortable-list.mjs +9 -9
- package/fesm2022/ngwr-speed-dial.mjs +3 -3
- package/fesm2022/ngwr-spinner.mjs +3 -3
- package/fesm2022/ngwr-splitter.mjs +3 -3
- package/fesm2022/ngwr-statistic.mjs +9 -9
- package/fesm2022/ngwr-stepper.mjs +6 -6
- package/fesm2022/ngwr-storage.mjs +3 -3
- package/fesm2022/ngwr-switch.mjs +3 -3
- package/fesm2022/ngwr-table.mjs +18 -18
- package/fesm2022/ngwr-tabs-router.mjs +3 -3
- package/fesm2022/ngwr-tabs.mjs +6 -6
- package/fesm2022/ngwr-textarea.mjs +3 -3
- package/fesm2022/ngwr-theme.mjs +3 -3
- package/fesm2022/ngwr-timeline.mjs +6 -6
- package/fesm2022/ngwr-toast.mjs +9 -9
- package/fesm2022/ngwr-toolbar.mjs +3 -3
- package/fesm2022/ngwr-tour.mjs +6 -6
- package/fesm2022/ngwr-transfer.mjs +3 -3
- package/fesm2022/ngwr-tree.mjs +3 -3
- package/fesm2022/ngwr-typography.mjs +3 -3
- package/fesm2022/ngwr-utils.mjs +31 -1
- package/fesm2022/ngwr-utils.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/fesm2022/ngwr-virtual-scroll.mjs +3 -3
- package/fesm2022/ngwr-window.mjs +12 -12
- package/llms-full.txt +12 -1
- package/mcp/server.js +15 -1
- package/mcp/tools.js +3 -3
- package/package.json +10 -1
- package/schematics/ng-add/index.js +2 -2
- package/schematics/use/symbol-map.json +1 -0
- package/skills/ngwr/SKILL.md +1 -1
- package/skills/ngwr/references/catalog.md +2 -0
- package/types/ngwr-bits-aurora.d.ts +11 -0
- package/types/ngwr-bits-border-glow-testing.d.ts +5 -6
- package/types/ngwr-bits-border-glow.d.ts +5 -1
- package/types/ngwr-bits-dot-grid.d.ts +7 -2
- package/types/ngwr-bits-iridescence-testing.d.ts +85 -0
- package/types/ngwr-bits-iridescence.d.ts +244 -0
- package/types/ngwr-bits-ripple-grid.d.ts +4 -7
- package/types/ngwr-bits-splash-cursor.d.ts +0 -1
- package/types/ngwr-bits-spotlight-card-testing.d.ts +3 -1
- package/types/ngwr-bits-star-border-testing.d.ts +7 -7
- package/types/ngwr-bits-threads.d.ts +11 -8
- package/types/ngwr-bits-waves.d.ts +6 -3
- package/types/ngwr-charts-core.d.ts +0 -10
- package/types/ngwr-color-picker.d.ts +0 -21
- package/types/ngwr-dropdown.d.ts +0 -52
- package/types/ngwr-editor.d.ts +2 -84
- package/types/ngwr-i18n.d.ts +0 -15
- package/types/ngwr-mention.d.ts +0 -13
- package/types/ngwr-popconfirm.d.ts +0 -33
- package/types/ngwr-popover.d.ts +0 -51
- package/types/ngwr-tour.d.ts +0 -2
- package/types/ngwr-utils.d.ts +49 -7
- package/types/ngwr-version.d.ts +1 -1
- package/types/ngwr-window.d.ts +0 -56
|
@@ -25,7 +25,7 @@ var WrSpotlightCard = class WrSpotlightCard {
|
|
|
25
25
|
}
|
|
26
26
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
27
27
|
minVersion: "12.0.0",
|
|
28
|
-
version: "22.2.
|
|
28
|
+
version: "22.2.4",
|
|
29
29
|
ngImport: i0,
|
|
30
30
|
type: WrSpotlightCard,
|
|
31
31
|
deps: [],
|
|
@@ -33,7 +33,7 @@ var WrSpotlightCard = class WrSpotlightCard {
|
|
|
33
33
|
});
|
|
34
34
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
35
35
|
minVersion: "17.1.0",
|
|
36
|
-
version: "22.2.
|
|
36
|
+
version: "22.2.4",
|
|
37
37
|
type: WrSpotlightCard,
|
|
38
38
|
isStandalone: true,
|
|
39
39
|
selector: "wr-spotlight-card",
|
|
@@ -56,7 +56,7 @@ var WrSpotlightCard = class WrSpotlightCard {
|
|
|
56
56
|
host: {
|
|
57
57
|
listeners: { "mousemove": "onMouseMove($event)" },
|
|
58
58
|
properties: {
|
|
59
|
-
"style.--wr-spotlight-color": "spotlightColor()",
|
|
59
|
+
"style.--wr-spotlight-input-color": "spotlightColor()",
|
|
60
60
|
"style.--wr-spotlight-radius": "radius() + \"%\""
|
|
61
61
|
},
|
|
62
62
|
classAttribute: "wr-spotlight-card"
|
|
@@ -64,13 +64,13 @@ var WrSpotlightCard = class WrSpotlightCard {
|
|
|
64
64
|
ngImport: i0,
|
|
65
65
|
template: "<ng-content />",
|
|
66
66
|
isInline: true,
|
|
67
|
-
styles: [".wr-spotlight-card{--wr-spotlight-x: 50%;--wr-spotlight-y: 50%;--wr-spotlight-color: light-dark( color-mix(in srgb, var(--wr-color-on-surface) 15%, transparent), color-mix(in srgb, var(--wr-color-on-surface) 22%, transparent) );--wr-spotlight-card-radius: var(--wr-border-radius-lg);--wr-spotlight-card-border: var(--wr-border-base);--wr-spotlight-card-padding: var(--wr-space-xl);position:relative;display:block;border-radius:var(--wr-spotlight-card-radius);border:1px solid var(--wr-spotlight-card-border);background-color:var(--wr-color-surface);padding:var(--wr-spotlight-card-padding);overflow:hidden;color:var(--wr-color-on-surface)}.wr-spotlight-card:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at var(--wr-spotlight-x) var(--wr-spotlight-y),var(--wr-spotlight-color),transparent var(--wr-spotlight-radius, 80%));opacity:0;transition:opacity .5s ease;pointer-events:none}.wr-spotlight-card:hover:before,.wr-spotlight-card:focus-within:before{opacity:.6}\n"],
|
|
67
|
+
styles: [".wr-spotlight-card{--wr-spotlight-x: 50%;--wr-spotlight-y: 50%;--wr-spotlight-input-color: initial;--wr-spotlight-color: light-dark( color-mix(in srgb, var(--wr-color-on-surface) 15%, transparent), color-mix(in srgb, var(--wr-color-on-surface) 22%, transparent) );--wr-spotlight-card-radius: var(--wr-border-radius-lg);--wr-spotlight-card-border: var(--wr-border-base);--wr-spotlight-card-padding: var(--wr-space-xl);position:relative;display:block;border-radius:var(--wr-spotlight-card-radius);border:1px solid var(--wr-spotlight-card-border);background-color:var(--wr-color-surface);padding:var(--wr-spotlight-card-padding);overflow:hidden;color:var(--wr-color-on-surface)}.wr-spotlight-card:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at var(--wr-spotlight-x) var(--wr-spotlight-y),var(--wr-spotlight-input-color, var(--wr-spotlight-color)),transparent var(--wr-spotlight-radius, 80%));opacity:0;transition:opacity .5s ease;pointer-events:none}.wr-spotlight-card:hover:before,.wr-spotlight-card:focus-within:before{opacity:.6}\n"],
|
|
68
68
|
encapsulation: i0.ViewEncapsulation.None
|
|
69
69
|
});
|
|
70
70
|
};
|
|
71
71
|
i0.ɵɵngDeclareClassMetadata({
|
|
72
72
|
minVersion: "12.0.0",
|
|
73
|
-
version: "22.2.
|
|
73
|
+
version: "22.2.4",
|
|
74
74
|
ngImport: i0,
|
|
75
75
|
type: WrSpotlightCard,
|
|
76
76
|
decorators: [{
|
|
@@ -81,11 +81,11 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
81
81
|
encapsulation: ViewEncapsulation.None,
|
|
82
82
|
host: {
|
|
83
83
|
class: "wr-spotlight-card",
|
|
84
|
-
"[style.--wr-spotlight-color]": "spotlightColor()",
|
|
84
|
+
"[style.--wr-spotlight-input-color]": "spotlightColor()",
|
|
85
85
|
"[style.--wr-spotlight-radius]": "radius() + \"%\"",
|
|
86
86
|
"(mousemove)": "onMouseMove($event)"
|
|
87
87
|
},
|
|
88
|
-
styles: [".wr-spotlight-card{--wr-spotlight-x: 50%;--wr-spotlight-y: 50%;--wr-spotlight-color: light-dark( color-mix(in srgb, var(--wr-color-on-surface) 15%, transparent), color-mix(in srgb, var(--wr-color-on-surface) 22%, transparent) );--wr-spotlight-card-radius: var(--wr-border-radius-lg);--wr-spotlight-card-border: var(--wr-border-base);--wr-spotlight-card-padding: var(--wr-space-xl);position:relative;display:block;border-radius:var(--wr-spotlight-card-radius);border:1px solid var(--wr-spotlight-card-border);background-color:var(--wr-color-surface);padding:var(--wr-spotlight-card-padding);overflow:hidden;color:var(--wr-color-on-surface)}.wr-spotlight-card:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at var(--wr-spotlight-x) var(--wr-spotlight-y),var(--wr-spotlight-color),transparent var(--wr-spotlight-radius, 80%));opacity:0;transition:opacity .5s ease;pointer-events:none}.wr-spotlight-card:hover:before,.wr-spotlight-card:focus-within:before{opacity:.6}\n"]
|
|
88
|
+
styles: [".wr-spotlight-card{--wr-spotlight-x: 50%;--wr-spotlight-y: 50%;--wr-spotlight-input-color: initial;--wr-spotlight-color: light-dark( color-mix(in srgb, var(--wr-color-on-surface) 15%, transparent), color-mix(in srgb, var(--wr-color-on-surface) 22%, transparent) );--wr-spotlight-card-radius: var(--wr-border-radius-lg);--wr-spotlight-card-border: var(--wr-border-base);--wr-spotlight-card-padding: var(--wr-space-xl);position:relative;display:block;border-radius:var(--wr-spotlight-card-radius);border:1px solid var(--wr-spotlight-card-border);background-color:var(--wr-color-surface);padding:var(--wr-spotlight-card-padding);overflow:hidden;color:var(--wr-color-on-surface)}.wr-spotlight-card:before{content:\"\";position:absolute;inset:0;background:radial-gradient(circle at var(--wr-spotlight-x) var(--wr-spotlight-y),var(--wr-spotlight-input-color, var(--wr-spotlight-color)),transparent var(--wr-spotlight-radius, 80%));opacity:0;transition:opacity .5s ease;pointer-events:none}.wr-spotlight-card:hover:before,.wr-spotlight-card:focus-within:before{opacity:.6}\n"]
|
|
89
89
|
}]
|
|
90
90
|
}],
|
|
91
91
|
propDecorators: {
|
|
@@ -153,7 +153,7 @@ var WrSpotlight = class WrSpotlight {
|
|
|
153
153
|
}
|
|
154
154
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
155
155
|
minVersion: "12.0.0",
|
|
156
|
-
version: "22.2.
|
|
156
|
+
version: "22.2.4",
|
|
157
157
|
ngImport: i0,
|
|
158
158
|
type: WrSpotlight,
|
|
159
159
|
deps: [],
|
|
@@ -161,7 +161,7 @@ var WrSpotlight = class WrSpotlight {
|
|
|
161
161
|
});
|
|
162
162
|
static ɵdir = i0.ɵɵngDeclareDirective({
|
|
163
163
|
minVersion: "17.1.0",
|
|
164
|
-
version: "22.2.
|
|
164
|
+
version: "22.2.4",
|
|
165
165
|
type: WrSpotlight,
|
|
166
166
|
isStandalone: true,
|
|
167
167
|
selector: "[wrSpotlight]",
|
|
@@ -190,7 +190,7 @@ var WrSpotlight = class WrSpotlight {
|
|
|
190
190
|
};
|
|
191
191
|
i0.ɵɵngDeclareClassMetadata({
|
|
192
192
|
minVersion: "12.0.0",
|
|
193
|
-
version: "22.2.
|
|
193
|
+
version: "22.2.4",
|
|
194
194
|
ngImport: i0,
|
|
195
195
|
type: WrSpotlight,
|
|
196
196
|
decorators: [{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-bits-spotlight-card.mjs","names":[],"sources":["../../../projects/lib/bits/spotlight-card/spotlight-card.ts","../../../projects/lib/bits/spotlight-card/spotlight.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Angular adaptation of the SpotlightCard effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/components/spotlight-card\n */\n\nimport { coerceNumberProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, ElementRef, inject, input } from '@angular/core';\n\n/**\n * Cursor-tracked spotlight card. A soft radial highlight follows the\n * pointer inside the card; fades in on hover, out on leave.\n *\n * Project any content as children — the spotlight overlay sits behind\n * the projected DOM (via a `::before` pseudo with `pointer-events: none`)\n * so clicks pass through naturally.\n *\n * @example\n * ```html\n * <wr-spotlight-card spotlightColor=\"rgba(120, 180, 255, 0.25)\">\n * <h3>Hover me</h3>\n * </wr-spotlight-card>\n * ```\n *\n * @see https://www.reactbits.dev/components/spotlight-card\n */\n@Component({\n selector: 'wr-spotlight-card',\n template: '<ng-content />',\n styleUrl: './spotlight-card.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-spotlight-card',\n '[style.--wr-spotlight-color]': 'spotlightColor()',\n '[style.--wr-spotlight-radius]': 'radius() + \"%\"',\n '(mousemove)': 'onMouseMove($event)',\n },\n})\nexport class WrSpotlightCard {\n /**\n * Highlight colour (any CSS colour). When unset, the theme decides:\n * a dark-ish glow on light surfaces, a light glow on dark.\n */\n readonly spotlightColor = input<string | null>(null);\n\n /** Where the spotlight fades out, as a percentage of the gradient. @default 80 */\n readonly radius = input(80, { transform: (v: unknown): number => coerceNumberProperty(v, 80) });\n\n private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n\n protected onMouseMove(event: MouseEvent): void {\n const host = this.el.nativeElement;\n const rect = host.getBoundingClientRect();\n host.style.setProperty('--wr-spotlight-x', `${event.clientX - rect.left}px`);\n host.style.setProperty('--wr-spotlight-y', `${event.clientY - rect.top}px`);\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { DestroyRef, Directive, ElementRef, NgZone, effect, inject, input } from '@angular/core';\n\n/**\n * Cursor-follow radial spotlight. Sets the `--wr-spotlight-x` and\n * `--wr-spotlight-y` CSS custom properties on the host based on the\n * pointer position so a `radial-gradient` (or `mask`) in your CSS can\n * follow the cursor.\n *\n * @example\n * ```html\n * <div class=\"card\" wrSpotlight>…</div>\n * ```\n *\n * ```scss\n * .card {\n * background: radial-gradient(\n * 400px circle at var(--wr-spotlight-x, 50%) var(--wr-spotlight-y, 50%),\n * color-mix(in srgb, var(--wr-color-primary) 15%, transparent),\n * transparent 60%\n * );\n * }\n * ```\n */\n@Directive({\n selector: '[wrSpotlight]',\n host: { '(pointermove)': 'onMove($event)', '(pointerleave)': 'onLeave()' },\n})\nexport class WrSpotlight {\n /** Optional default coordinates when no pointer is over the host. */\n readonly resetX = input<string>('50%');\n /**\n * Vertical half of that resting position. Same units as `resetX`.\n */\n readonly resetY = input<string>('50%');\n\n private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n\n /** Whether the pointer is over the host — the rest position waits until it leaves. */\n private hovering = false;\n\n constructor() {\n const host = this.el.nativeElement;\n // An effect, not a constructor write: signal inputs are not bound while the\n // constructor runs, so reading them there always saw the `50%` default and a\n // custom rest point only took effect after the pointer had left once. Same\n // trap `WrTilt`'s constructor describes. Tracking them also follows a rest\n // point changed at runtime.\n effect(() => {\n const x = this.resetX();\n const y = this.resetY();\n if (this.hovering) return;\n host.style.setProperty('--wr-spotlight-x', x);\n host.style.setProperty('--wr-spotlight-y', y);\n });\n this.destroyRef.onDestroy(() => {\n host.style.removeProperty('--wr-spotlight-x');\n host.style.removeProperty('--wr-spotlight-y');\n });\n }\n\n /** @internal */\n protected onMove(event: PointerEvent): void {\n this.hovering = true;\n this.zone.runOutsideAngular(() => {\n const host = this.el.nativeElement;\n const rect = host.getBoundingClientRect();\n const x = ((event.clientX - rect.left) / rect.width) * 100;\n const y = ((event.clientY - rect.top) / rect.height) * 100;\n host.style.setProperty('--wr-spotlight-x', `${x}%`);\n host.style.setProperty('--wr-spotlight-y', `${y}%`);\n });\n }\n\n /** @internal */\n protected onLeave(): void {\n this.hovering = false;\n const host = this.el.nativeElement;\n host.style.setProperty('--wr-spotlight-x', this.resetX());\n host.style.setProperty('--wr-spotlight-y', this.resetY());\n }\n}\n"],"mappings":";;;;;;;;;;;;AA0CA,IAAa,kBAAb,MAAa,gBAAe;CAKjB,iBAAiB,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,iBAAA,CAAA,+BAAA,CAAA,CAAA;CAG3C,SAAS,MAAM,IAAE;EAAA,GAAA,YAAA,EAAA,WAAA,SAAA,+BAAA,CAAA;EAAI,YAAY,MAAuB,qBAAqB,GAAG,EAAE;CAAC,CAAA;CAE3E,KAAK,OAAgC,UAAU;CAEtD,YAAY,OAAiB;EACrC,MAAM,OAAO,KAAK,GAAG;EACrB,MAAM,OAAO,KAAK,sBAAqB;EACvC,KAAK,MAAM,YAAY,oBAAoB,GAAG,MAAM,UAAU,KAAK,KAAI,GAAI;EAC3E,KAAK,MAAM,YAAY,oBAAoB,GAAG,MAAM,UAAU,KAAK,IAAG,GAAI;CAC5E;CAjBW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAe,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAf,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAe,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,gBAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,QAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA;GAAA,WAAA,EAAA,aAAA,sBAAA;GAAA,YAAA;IAAA,
|
|
1
|
+
{"version":3,"file":"ngwr-bits-spotlight-card.mjs","names":[],"sources":["../../../projects/lib/bits/spotlight-card/spotlight-card.ts","../../../projects/lib/bits/spotlight-card/spotlight.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n *\n * Angular adaptation of the SpotlightCard effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/components/spotlight-card\n */\n\nimport { coerceNumberProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, ElementRef, inject, input } from '@angular/core';\n\n/**\n * Cursor-tracked spotlight card. A soft radial highlight follows the\n * pointer inside the card; fades in on hover, out on leave.\n *\n * Project any content as children — the spotlight overlay sits behind\n * the projected DOM (via a `::before` pseudo with `pointer-events: none`)\n * so clicks pass through naturally.\n *\n * @example\n * ```html\n * <wr-spotlight-card spotlightColor=\"rgba(120, 180, 255, 0.25)\">\n * <h3>Hover me</h3>\n * </wr-spotlight-card>\n * ```\n *\n * @see https://www.reactbits.dev/components/spotlight-card\n */\n@Component({\n selector: 'wr-spotlight-card',\n template: '<ng-content />',\n styleUrl: './spotlight-card.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-spotlight-card',\n '[style.--wr-spotlight-input-color]': 'spotlightColor()',\n '[style.--wr-spotlight-radius]': 'radius() + \"%\"',\n '(mousemove)': 'onMouseMove($event)',\n },\n})\nexport class WrSpotlightCard {\n /**\n * Highlight colour (any CSS colour). When unset, the theme decides:\n * a dark-ish glow on light surfaces, a light glow on dark.\n */\n readonly spotlightColor = input<string | null>(null);\n\n /** Where the spotlight fades out, as a percentage of the gradient. @default 80 */\n readonly radius = input(80, { transform: (v: unknown): number => coerceNumberProperty(v, 80) });\n\n private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n\n protected onMouseMove(event: MouseEvent): void {\n const host = this.el.nativeElement;\n const rect = host.getBoundingClientRect();\n host.style.setProperty('--wr-spotlight-x', `${event.clientX - rect.left}px`);\n host.style.setProperty('--wr-spotlight-y', `${event.clientY - rect.top}px`);\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { DestroyRef, Directive, ElementRef, NgZone, effect, inject, input } from '@angular/core';\n\n/**\n * Cursor-follow radial spotlight. Sets the `--wr-spotlight-x` and\n * `--wr-spotlight-y` CSS custom properties on the host based on the\n * pointer position so a `radial-gradient` (or `mask`) in your CSS can\n * follow the cursor.\n *\n * @example\n * ```html\n * <div class=\"card\" wrSpotlight>…</div>\n * ```\n *\n * ```scss\n * .card {\n * background: radial-gradient(\n * 400px circle at var(--wr-spotlight-x, 50%) var(--wr-spotlight-y, 50%),\n * color-mix(in srgb, var(--wr-color-primary) 15%, transparent),\n * transparent 60%\n * );\n * }\n * ```\n */\n@Directive({\n selector: '[wrSpotlight]',\n host: { '(pointermove)': 'onMove($event)', '(pointerleave)': 'onLeave()' },\n})\nexport class WrSpotlight {\n /** Optional default coordinates when no pointer is over the host. */\n readonly resetX = input<string>('50%');\n /**\n * Vertical half of that resting position. Same units as `resetX`.\n */\n readonly resetY = input<string>('50%');\n\n private readonly el = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n\n /** Whether the pointer is over the host — the rest position waits until it leaves. */\n private hovering = false;\n\n constructor() {\n const host = this.el.nativeElement;\n // An effect, not a constructor write: signal inputs are not bound while the\n // constructor runs, so reading them there always saw the `50%` default and a\n // custom rest point only took effect after the pointer had left once. Same\n // trap `WrTilt`'s constructor describes. Tracking them also follows a rest\n // point changed at runtime.\n effect(() => {\n const x = this.resetX();\n const y = this.resetY();\n if (this.hovering) return;\n host.style.setProperty('--wr-spotlight-x', x);\n host.style.setProperty('--wr-spotlight-y', y);\n });\n this.destroyRef.onDestroy(() => {\n host.style.removeProperty('--wr-spotlight-x');\n host.style.removeProperty('--wr-spotlight-y');\n });\n }\n\n /** @internal */\n protected onMove(event: PointerEvent): void {\n this.hovering = true;\n this.zone.runOutsideAngular(() => {\n const host = this.el.nativeElement;\n const rect = host.getBoundingClientRect();\n const x = ((event.clientX - rect.left) / rect.width) * 100;\n const y = ((event.clientY - rect.top) / rect.height) * 100;\n host.style.setProperty('--wr-spotlight-x', `${x}%`);\n host.style.setProperty('--wr-spotlight-y', `${y}%`);\n });\n }\n\n /** @internal */\n protected onLeave(): void {\n this.hovering = false;\n const host = this.el.nativeElement;\n host.style.setProperty('--wr-spotlight-x', this.resetX());\n host.style.setProperty('--wr-spotlight-y', this.resetY());\n }\n}\n"],"mappings":"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"}
|
|
@@ -29,7 +29,7 @@ var WrStarBorderHarness = class WrStarBorderHarness extends ComponentHarness {
|
|
|
29
29
|
return (await this.host()).hasClass("wr-star-border--single");
|
|
30
30
|
}
|
|
31
31
|
async getRayColor() {
|
|
32
|
-
return this.hostStyle("--wr-star-border-color");
|
|
32
|
+
return this.hostStyle("--wr-star-border-input-color");
|
|
33
33
|
}
|
|
34
34
|
async getSpeedSeconds() {
|
|
35
35
|
const raw = await this.hostStyle("--wr-star-border-speed");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-bits-star-border-testing.mjs","names":[],"sources":["../../../projects/lib/bits/star-border/testing/wr-star-border-harness.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrStarBorderHarnessFilters } from './types';\n\n/** Read one declaration out of an inline `style` attribute, or `null` when it is not there. */\nfunction inlineStyle(style: string | null, property: string): string | null {\n for (const declaration of (style ?? '').split(';')) {\n const colon = declaration.indexOf(':');\n if (colon === -1) continue;\n if (declaration.slice(0, colon).trim() !== property) continue;\n return declaration.slice(colon + 1).trim();\n }\n return null;\n}\n\n/**\n * Test harness for `<wr-star-border>` and for the `[wr-star-border]` attribute form.\n *\n * **There is no `getRayCount()`, and no `hasTopRay()`.** `rays=\"single\"` does not remove\n * the top ray: both spans are always in the DOM and the stylesheet hides one with\n * `display: none`. A count would answer 2 in both modes — flatly contradicting the input\n * that was set — so the variant is read as {@link isSingleRay}, off the modifier class\n * the CSS itself keys on. That class is public API, and it is the honest observable.\n *\n * Nothing here reports on the motion either. The sweep is a `@keyframes` rule in a\n * stylesheet a unit test never loads, so `animationName` is `''` and `getAnimations()` is\n * empty whether the component works or not; `mode=\"hover\"` is a bare `:hover` selector\n * with no state behind it, which is why there is no `hover()` to pair with anything; and\n * the reduced-motion escape is a media query with no mirror in TypeScript, so the DOM is\n * identical for a user who asked for less motion and one who did not.\n *\n * **The inline values are read off the `style` attribute rather than through\n * `getCssValue()`, and that is not a jsdom concession.** The stylesheet declares its own\n * `--wr-star-border-color` and `--wr-star-border-speed` on `.wr-star-border`, so in a\n * real browser a computed read answers with the stylesheet's default at exactly the\n * moment the host binding is the thing that broke. What the component wrote is the only\n * value nothing else can forge.\n *\n * On the attribute form the host's `[class]` binding MERGES with the classes the consumer\n * already put there, so read the variants through {@link isHoverOnly} and\n * {@link isSingleRay} rather than comparing a whole `className` — the latter passes only\n * as long as the host happens to carry nothing else.\n *\n * @example\n * ```ts\n * const loader = TestbedHarnessEnvironment.loader(fixture);\n * const border = await loader.getHarness(WrStarBorderHarness);\n *\n * expect(await border.getText()).toBe('Upgrade');\n * expect(await border.getSpeedSeconds()).toBe(6);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrStarBorderHarness extends ComponentHarness {\n static hostSelector = 'wr-star-border, [wr-star-border]';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrStarBorderHarnessFilters = {}): HarnessPredicate<WrStarBorderHarness> {\n return new HarnessPredicate(WrStarBorderHarness, options).addOption('text', options.text, (harness, text) =>\n HarnessPredicate.stringMatches(harness.getText(), text)\n );\n }\n\n /**\n * The projected content's text, trimmed.\n *\n * Read from the inner panel rather than from the host, which would pick up anything the\n * rays ever came to hold. They are empty spans today, so the two agree — but the panel\n * is the slot the content was put in, and it is the thing a reader actually sees.\n */\n async getText(): Promise<string> {\n return (await this.locatorFor('.wr-star-border__inner')()).text();\n }\n\n /** Whether the rays wait for the pointer (`mode=\"hover\"`) instead of running always. */\n async isHoverOnly(): Promise<boolean> {\n return (await this.host()).hasClass('wr-star-border--hover');\n }\n\n /**\n * Whether only the bottom ray is drawn (`rays=\"single\"`).\n *\n * From the modifier class, because the DOM cannot be asked: both ray spans are rendered\n * in both modes and the top one is hidden in CSS. See this class's docs.\n */\n async isSingleRay(): Promise<boolean> {\n return (await this.host()).hasClass('wr-star-border--single');\n }\n\n /**\n * The ray colour as the consumer passed it — a CSS value, not a resolved one — or\n * `null` when none was given.\n *\n * `null` is the common case and a real answer rather than a missing one: with no\n * `[color]` the theme decides, primary on a light surface and white on a dark one, and\n * a component that started writing a colour of its own there would silently take that\n * choice away.\n */\n async getRayColor(): Promise<string | null> {\n return this.hostStyle('--wr-star-border-color');\n }\n\n /**\n * How long one ray sweep takes, in seconds.\n *\n * Refuses a value that lost its unit rather than parsing the number out of it. The\n * component builds this string from a plain number input and the stylesheet drops it\n * straight into `animation-duration`, where a bare `6` is invalid — the declaration is\n * discarded, the stylesheet's own default takes over, and nothing about the page looks\n * wrong. A `parseFloat` would report 6 either way.\n */\n async getSpeedSeconds(): Promise<number> {\n const raw = await this.hostStyle('--wr-star-border-speed');\n const match = /^(-?\\d*\\.?\\d+)s$/.exec(raw ?? '');\n\n if (!match) {\n throw new Error(\n `WrStarBorderHarness.getSpeedSeconds(): \\`--wr-star-border-speed\\` reads \"${raw}\" rather than a value in ` +\n 'seconds. The stylesheet uses it as an animation-duration, so anything without the unit is an invalid ' +\n 'declaration and the rays fall back to the default sweep.'\n );\n }\n return Number(match[1]);\n }\n\n /**\n * How far the rays bleed past the inner panel, in pixels.\n *\n * Read off the host's `padding`, which is where `[thickness]` lands: the component\n * writes the SHORTHAND, `<n>px 0`. Worth knowing on the attribute form — an inline\n * shorthand wins the cascade, so a `<button wr-star-border>` loses whatever padding its\n * own classes gave it, horizontal included. The visible padding lives on the inner\n * panel, so this is a bleed measurement rather than a spacing one.\n *\n * Throws when the host's padding is not that shape, which means either the binding\n * stopped arriving or something else is writing the property inline.\n */\n async getThickness(): Promise<number> {\n const raw = await this.hostStyle('padding');\n const match = /^(-?\\d*\\.?\\d+)px\\s+0(?:px)?$/.exec(raw ?? '');\n\n if (!match) {\n throw new Error(\n `WrStarBorderHarness.getThickness(): the host's inline padding reads \"${raw}\" rather than the ` +\n '`<n>px 0` shorthand the component writes from [thickness]. Either the binding is gone, or something ' +\n 'else is setting padding inline on the same element.'\n );\n }\n return Number(match[1]);\n }\n\n /**\n * Whether every ray is kept out of the accessible tree.\n *\n * The one question worth asking about both spans at once — not how many there are, but\n * whether either has stopped being decoration. They carry no text and mean nothing, so\n * a ray that reached a screen reader would be an empty announcement in the middle of a\n * button. Answered for all of them together, so one ray losing the attribute is enough\n * to report `false`.\n */\n async areRaysDecorative(): Promise<boolean> {\n const rays = await this.locatorForAll('.wr-star-border__ray')();\n\n if (rays.length === 0) {\n throw new Error(\n 'WrStarBorderHarness.areRaysDecorative(): there are no rays at all. Both spans are rendered in every ' +\n 'mode — `rays=\"single\"` hides one in CSS rather than dropping it — so none of them is a broken template ' +\n 'rather than a variant.'\n );\n }\n\n const hidden = await Promise.all(rays.map(ray => ray.getAttribute('aria-hidden')));\n return hidden.every(value => value === 'true');\n }\n\n /** Read one declaration off the host's inline `style` — see this class's docs for why not `getCssValue()`. */\n private async hostStyle(property: string): Promise<string | null> {\n return inlineStyle(await (await this.host()).getAttribute('style'), property);\n }\n}\n"],"mappings":";;;;;;;AAYA,SAAS,YAAY,OAAsB,UAAgB;CACzD,KAAK,MAAM,gBAAgB,SAAS,GAAA,CAAI,MAAM,GAAG,GAAG;EAClD,MAAM,QAAQ,YAAY,QAAQ,GAAG;EACrC,IAAI,UAAU,IAAI;EAClB,IAAI,YAAY,MAAM,GAAG,KAAK,CAAC,CAAC,KAAI,MAAO,UAAU;EACrD,OAAO,YAAY,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAI;CAC1C;CACA,OAAO;AACT;AAyCA,IAAa,sBAAb,MAAa,4BAA4B,iBAAgB;CACvD,OAAO,eAAe;CAGtB,OAAO,KAAK,UAAsC,CAAA,GAAE;EAClD,OAAO,IAAI,iBAAiB,qBAAqB,OAAO,CAAC,CAAC,UAAU,QAAQ,QAAQ,OAAO,SAAS,SAClG,iBAAiB,cAAc,QAAQ,QAAO,GAAI,IAAI,CAAC;CAE3D;CASA,MAAM,UAAO;EACX,QAAQ,MAAM,KAAK,WAAW,wBAAwB,CAAC,CAAA,EAAA,CAAI,KAAI;CACjE;CAGA,MAAM,cAAW;EACf,QAAQ,MAAM,KAAK,KAAI,EAAA,CAAI,SAAS,uBAAuB;CAC7D;CAQA,MAAM,cAAW;EACf,QAAQ,MAAM,KAAK,KAAI,EAAA,CAAI,SAAS,wBAAwB;CAC9D;CAWA,MAAM,cAAW;EACf,OAAO,KAAK,UAAU,wBAAwB;CAChD;CAWA,MAAM,kBAAe;EACnB,MAAM,MAAM,MAAM,KAAK,UAAU,wBAAwB;EACzD,MAAM,QAAQ,mBAAmB,KAAK,OAAO,EAAE;EAE/C,IAAI,CAAC,OACH,MAAM,IAAI,MACR,4EAA4E,IAAG,uLAEnB;EAGhE,OAAO,OAAO,MAAM,EAAE;CACxB;CAcA,MAAM,eAAY;EAChB,MAAM,MAAM,MAAM,KAAK,UAAU,SAAS;EAC1C,MAAM,QAAQ,+BAA+B,KAAK,OAAO,EAAE;EAE3D,IAAI,CAAC,OACH,MAAM,IAAI,MACR,wEAAwE,IAAG,4KAEpB;EAG3D,OAAO,OAAO,MAAM,EAAE;CACxB;CAWA,MAAM,oBAAiB;EACrB,MAAM,OAAO,MAAM,KAAK,cAAc,sBAAsB,CAAC,CAAA;EAE7D,IAAI,KAAK,WAAW,GAClB,MAAM,IAAI,MACR,qOAE0B;EAK9B,QAAO,MADc,QAAQ,IAAI,KAAK,KAAI,QAAO,IAAI,aAAa,aAAa,CAAC,CAAC,EAC1E,CAAO,OAAM,UAAS,UAAU,MAAM;CAC/C;CAGQ,MAAM,UAAU,UAAgB;EACtC,OAAO,YAAY,OAAO,MAAM,KAAK,KAAI,EAAA,CAAI,aAAa,OAAO,GAAG,QAAQ;CAC9E"}
|
|
1
|
+
{"version":3,"file":"ngwr-bits-star-border-testing.mjs","names":[],"sources":["../../../projects/lib/bits/star-border/testing/wr-star-border-harness.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrStarBorderHarnessFilters } from './types';\n\n/** Read one declaration out of an inline `style` attribute, or `null` when it is not there. */\nfunction inlineStyle(style: string | null, property: string): string | null {\n for (const declaration of (style ?? '').split(';')) {\n const colon = declaration.indexOf(':');\n if (colon === -1) continue;\n if (declaration.slice(0, colon).trim() !== property) continue;\n return declaration.slice(colon + 1).trim();\n }\n return null;\n}\n\n/**\n * Test harness for `<wr-star-border>` and for the `[wr-star-border]` attribute form.\n *\n * **There is no `getRayCount()`, and no `hasTopRay()`.** `rays=\"single\"` does not remove\n * the top ray: both spans are always in the DOM and the stylesheet hides one with\n * `display: none`. A count would answer 2 in both modes — flatly contradicting the input\n * that was set — so the variant is read as {@link isSingleRay}, off the modifier class\n * the CSS itself keys on. That class is public API, and it is the honest observable.\n *\n * Nothing here reports on the motion either. The sweep is a `@keyframes` rule in a\n * stylesheet a unit test never loads, so `animationName` is `''` and `getAnimations()` is\n * empty whether the component works or not; `mode=\"hover\"` is a bare `:hover` selector\n * with no state behind it, which is why there is no `hover()` to pair with anything; and\n * the reduced-motion escape is a media query with no mirror in TypeScript, so the DOM is\n * identical for a user who asked for less motion and one who did not.\n *\n * **The inline values are read off the `style` attribute rather than through\n * `getCssValue()`, and that is not a jsdom concession.** The stylesheet declares its own\n * `--wr-star-border-speed` on `.wr-star-border`, so in a real browser a computed read\n * answers with the stylesheet's default at exactly the moment the host binding is the\n * thing that broke. The colour goes on `--wr-star-border-input-color`, a variable of the\n * component's own. What the component wrote is the only value nothing else can forge.\n *\n * On the attribute form the host's `[class]` binding MERGES with the classes the consumer\n * already put there, so read the variants through {@link isHoverOnly} and\n * {@link isSingleRay} rather than comparing a whole `className` — the latter passes only\n * as long as the host happens to carry nothing else.\n *\n * @example\n * ```ts\n * const loader = TestbedHarnessEnvironment.loader(fixture);\n * const border = await loader.getHarness(WrStarBorderHarness);\n *\n * expect(await border.getText()).toBe('Upgrade');\n * expect(await border.getSpeedSeconds()).toBe(6);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrStarBorderHarness extends ComponentHarness {\n static hostSelector = 'wr-star-border, [wr-star-border]';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrStarBorderHarnessFilters = {}): HarnessPredicate<WrStarBorderHarness> {\n return new HarnessPredicate(WrStarBorderHarness, options).addOption('text', options.text, (harness, text) =>\n HarnessPredicate.stringMatches(harness.getText(), text)\n );\n }\n\n /**\n * The projected content's text, trimmed.\n *\n * Read from the inner panel rather than from the host, which would pick up anything the\n * rays ever came to hold. They are empty spans today, so the two agree — but the panel\n * is the slot the content was put in, and it is the thing a reader actually sees.\n */\n async getText(): Promise<string> {\n return (await this.locatorFor('.wr-star-border__inner')()).text();\n }\n\n /** Whether the rays wait for the pointer (`mode=\"hover\"`) instead of running always. */\n async isHoverOnly(): Promise<boolean> {\n return (await this.host()).hasClass('wr-star-border--hover');\n }\n\n /**\n * Whether only the bottom ray is drawn (`rays=\"single\"`).\n *\n * From the modifier class, because the DOM cannot be asked: both ray spans are rendered\n * in both modes and the top one is hidden in CSS. See this class's docs.\n */\n async isSingleRay(): Promise<boolean> {\n return (await this.host()).hasClass('wr-star-border--single');\n }\n\n /**\n * The ray colour as the consumer passed it — a CSS value, not a resolved one — or\n * `null` when none was given.\n *\n * `null` is the common case and a real answer rather than a missing one: with no\n * `[color]` the `--wr-star-border-color` hook decides, by default primary on a light\n * surface and white on a dark one, and a component that started writing a colour of its\n * own there would silently take that choice away.\n */\n async getRayColor(): Promise<string | null> {\n return this.hostStyle('--wr-star-border-input-color');\n }\n\n /**\n * How long one ray sweep takes, in seconds.\n *\n * Refuses a value that lost its unit rather than parsing the number out of it. The\n * component builds this string from a plain number input and the stylesheet drops it\n * straight into `animation-duration`, where a bare `6` is invalid — the declaration is\n * discarded, the stylesheet's own default takes over, and nothing about the page looks\n * wrong. A `parseFloat` would report 6 either way.\n */\n async getSpeedSeconds(): Promise<number> {\n const raw = await this.hostStyle('--wr-star-border-speed');\n const match = /^(-?\\d*\\.?\\d+)s$/.exec(raw ?? '');\n\n if (!match) {\n throw new Error(\n `WrStarBorderHarness.getSpeedSeconds(): \\`--wr-star-border-speed\\` reads \"${raw}\" rather than a value in ` +\n 'seconds. The stylesheet uses it as an animation-duration, so anything without the unit is an invalid ' +\n 'declaration and the rays fall back to the default sweep.'\n );\n }\n return Number(match[1]);\n }\n\n /**\n * How far the rays bleed past the inner panel, in pixels.\n *\n * Read off the host's `padding`, which is where `[thickness]` lands: the component\n * writes the SHORTHAND, `<n>px 0`. Worth knowing on the attribute form — an inline\n * shorthand wins the cascade, so a `<button wr-star-border>` loses whatever padding its\n * own classes gave it, horizontal included. The visible padding lives on the inner\n * panel, so this is a bleed measurement rather than a spacing one.\n *\n * Throws when the host's padding is not that shape, which means either the binding\n * stopped arriving or something else is writing the property inline.\n */\n async getThickness(): Promise<number> {\n const raw = await this.hostStyle('padding');\n const match = /^(-?\\d*\\.?\\d+)px\\s+0(?:px)?$/.exec(raw ?? '');\n\n if (!match) {\n throw new Error(\n `WrStarBorderHarness.getThickness(): the host's inline padding reads \"${raw}\" rather than the ` +\n '`<n>px 0` shorthand the component writes from [thickness]. Either the binding is gone, or something ' +\n 'else is setting padding inline on the same element.'\n );\n }\n return Number(match[1]);\n }\n\n /**\n * Whether every ray is kept out of the accessible tree.\n *\n * The one question worth asking about both spans at once — not how many there are, but\n * whether either has stopped being decoration. They carry no text and mean nothing, so\n * a ray that reached a screen reader would be an empty announcement in the middle of a\n * button. Answered for all of them together, so one ray losing the attribute is enough\n * to report `false`.\n */\n async areRaysDecorative(): Promise<boolean> {\n const rays = await this.locatorForAll('.wr-star-border__ray')();\n\n if (rays.length === 0) {\n throw new Error(\n 'WrStarBorderHarness.areRaysDecorative(): there are no rays at all. Both spans are rendered in every ' +\n 'mode — `rays=\"single\"` hides one in CSS rather than dropping it — so none of them is a broken template ' +\n 'rather than a variant.'\n );\n }\n\n const hidden = await Promise.all(rays.map(ray => ray.getAttribute('aria-hidden')));\n return hidden.every(value => value === 'true');\n }\n\n /** Read one declaration off the host's inline `style` — see this class's docs for why not `getCssValue()`. */\n private async hostStyle(property: string): Promise<string | null> {\n return inlineStyle(await (await this.host()).getAttribute('style'), property);\n }\n}\n"],"mappings":";;;;;;;AAYA,SAAS,YAAY,OAAsB,UAAgB;CACzD,KAAK,MAAM,gBAAgB,SAAS,GAAA,CAAI,MAAM,GAAG,GAAG;EAClD,MAAM,QAAQ,YAAY,QAAQ,GAAG;EACrC,IAAI,UAAU,IAAI;EAClB,IAAI,YAAY,MAAM,GAAG,KAAK,CAAC,CAAC,KAAI,MAAO,UAAU;EACrD,OAAO,YAAY,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAI;CAC1C;CACA,OAAO;AACT;AAyCA,IAAa,sBAAb,MAAa,4BAA4B,iBAAgB;CACvD,OAAO,eAAe;CAGtB,OAAO,KAAK,UAAsC,CAAA,GAAE;EAClD,OAAO,IAAI,iBAAiB,qBAAqB,OAAO,CAAC,CAAC,UAAU,QAAQ,QAAQ,OAAO,SAAS,SAClG,iBAAiB,cAAc,QAAQ,QAAO,GAAI,IAAI,CAAC;CAE3D;CASA,MAAM,UAAO;EACX,QAAQ,MAAM,KAAK,WAAW,wBAAwB,CAAC,CAAA,EAAA,CAAI,KAAI;CACjE;CAGA,MAAM,cAAW;EACf,QAAQ,MAAM,KAAK,KAAI,EAAA,CAAI,SAAS,uBAAuB;CAC7D;CAQA,MAAM,cAAW;EACf,QAAQ,MAAM,KAAK,KAAI,EAAA,CAAI,SAAS,wBAAwB;CAC9D;CAWA,MAAM,cAAW;EACf,OAAO,KAAK,UAAU,8BAA8B;CACtD;CAWA,MAAM,kBAAe;EACnB,MAAM,MAAM,MAAM,KAAK,UAAU,wBAAwB;EACzD,MAAM,QAAQ,mBAAmB,KAAK,OAAO,EAAE;EAE/C,IAAI,CAAC,OACH,MAAM,IAAI,MACR,4EAA4E,IAAG,uLAEnB;EAGhE,OAAO,OAAO,MAAM,EAAE;CACxB;CAcA,MAAM,eAAY;EAChB,MAAM,MAAM,MAAM,KAAK,UAAU,SAAS;EAC1C,MAAM,QAAQ,+BAA+B,KAAK,OAAO,EAAE;EAE3D,IAAI,CAAC,OACH,MAAM,IAAI,MACR,wEAAwE,IAAG,4KAEpB;EAG3D,OAAO,OAAO,MAAM,EAAE;CACxB;CAWA,MAAM,oBAAiB;EACrB,MAAM,OAAO,MAAM,KAAK,cAAc,sBAAsB,CAAC,CAAA;EAE7D,IAAI,KAAK,WAAW,GAClB,MAAM,IAAI,MACR,qOAE0B;EAK9B,QAAO,MADc,QAAQ,IAAI,KAAK,KAAI,QAAO,IAAI,aAAa,aAAa,CAAC,CAAC,EAC1E,CAAO,OAAM,UAAS,UAAU,MAAM;CAC/C;CAGQ,MAAM,UAAU,UAAgB;EACtC,OAAO,YAAY,OAAO,MAAM,KAAK,KAAI,EAAA,CAAI,aAAa,OAAO,GAAG,QAAQ;CAC9E"}
|
|
@@ -33,7 +33,7 @@ var WrStarBorder = class WrStarBorder {
|
|
|
33
33
|
};
|
|
34
34
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
35
35
|
minVersion: "12.0.0",
|
|
36
|
-
version: "22.2.
|
|
36
|
+
version: "22.2.4",
|
|
37
37
|
ngImport: i0,
|
|
38
38
|
type: WrStarBorder,
|
|
39
39
|
deps: [],
|
|
@@ -41,7 +41,7 @@ var WrStarBorder = class WrStarBorder {
|
|
|
41
41
|
});
|
|
42
42
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
43
43
|
minVersion: "17.1.0",
|
|
44
|
-
version: "22.2.
|
|
44
|
+
version: "22.2.4",
|
|
45
45
|
type: WrStarBorder,
|
|
46
46
|
isStandalone: true,
|
|
47
47
|
selector: "wr-star-border, [wr-star-border]",
|
|
@@ -84,19 +84,19 @@ var WrStarBorder = class WrStarBorder {
|
|
|
84
84
|
},
|
|
85
85
|
host: { properties: {
|
|
86
86
|
"class": "classes()",
|
|
87
|
-
"style.--wr-star-border-color": "color()",
|
|
87
|
+
"style.--wr-star-border-input-color": "color()",
|
|
88
88
|
"style.--wr-star-border-speed": "speed() + \"s\"",
|
|
89
89
|
"style.padding": "thickness() + \"px 0\""
|
|
90
90
|
} },
|
|
91
91
|
ngImport: i0,
|
|
92
92
|
template: "<span class=\"wr-star-border__ray wr-star-border__ray--bottom\" aria-hidden=\"true\"></span>\n<span class=\"wr-star-border__ray wr-star-border__ray--top\" aria-hidden=\"true\"></span>\n<span class=\"wr-star-border__inner\"><ng-content /></span>\n",
|
|
93
|
-
styles: [".wr-star-border{--wr-star-border-color: light-dark(var(--wr-color-primary), #ffffff);--wr-star-border-speed: 6s;--wr-star-border-radius: 20px;--wr-star-border-opacity: 1;display:inline-block;position:relative;border:0;background:transparent;border-radius:var(--wr-star-border-radius);overflow:hidden;cursor:inherit}.wr-star-border__ray{position:absolute;display:block;width:300%;height:50%;opacity:var(--wr-star-border-opacity);border-radius:50%;background:radial-gradient(circle,var(--wr-star-border-color),light-dark(color-mix(in srgb,var(--wr-star-border-color) 37.5%,transparent),transparent) 10%,transparent 16%);animation:wr-star-border-bottom var(--wr-star-border-speed) linear infinite alternate;z-index:0}.wr-star-border__ray--bottom{bottom:-12px;right:-250%}.wr-star-border__ray--top{top:-12px;left:-250%;animation-name:wr-star-border-top}.wr-star-border--single .wr-star-border__ray--top{display:none}.wr-star-border--hover .wr-star-border__ray{opacity:0;animation:none}.wr-star-border--hover:hover .wr-star-border__ray{animation:wr-star-border-bottom var(--wr-star-border-speed) linear infinite alternate}.wr-star-border--hover:hover .wr-star-border__ray--top{animation-name:wr-star-border-top}.wr-star-border__inner{position:relative;display:block;border:1px solid var(--wr-border-base);background:var(--wr-color-surface);color:var(--wr-color-on-surface);font-size:1rem;text-align:center;padding:var(--wr-space-lg) 1.625rem;border-radius:var(--wr-star-border-radius);z-index:1}@keyframes wr-star-border-bottom{0%{transform:translate(0);opacity:1}to{transform:translate(-100%);opacity:0}}@keyframes wr-star-border-top{0%{transform:translate(0);opacity:1}to{transform:translate(100%);opacity:0}}@media(prefers-reduced-motion:reduce){.wr-star-border__ray{animation:none!important;opacity:0!important}}\n"],
|
|
93
|
+
styles: [".wr-star-border{--wr-star-border-color: light-dark(var(--wr-color-primary), #ffffff);--wr-star-border-input-color: initial;--wr-star-border-speed: 6s;--wr-star-border-radius: 20px;--wr-star-border-opacity: 1;display:inline-block;position:relative;border:0;background:transparent;border-radius:var(--wr-star-border-radius);overflow:hidden;cursor:inherit}.wr-star-border__ray{position:absolute;display:block;width:300%;height:50%;opacity:var(--wr-star-border-opacity);border-radius:50%;background:radial-gradient(circle,var(--wr-star-border-input-color, var(--wr-star-border-color)),light-dark(color-mix(in srgb,var(--wr-star-border-input-color, var(--wr-star-border-color)) 37.5%,transparent),transparent) 10%,transparent 16%);animation:wr-star-border-bottom var(--wr-star-border-speed) linear infinite alternate;z-index:0}.wr-star-border__ray--bottom{bottom:-12px;right:-250%}.wr-star-border__ray--top{top:-12px;left:-250%;animation-name:wr-star-border-top}.wr-star-border--single .wr-star-border__ray--top{display:none}.wr-star-border--hover .wr-star-border__ray{opacity:0;animation:none}.wr-star-border--hover:hover .wr-star-border__ray{animation:wr-star-border-bottom var(--wr-star-border-speed) linear infinite alternate}.wr-star-border--hover:hover .wr-star-border__ray--top{animation-name:wr-star-border-top}.wr-star-border__inner{position:relative;display:block;border:1px solid var(--wr-border-base);background:var(--wr-color-surface);color:var(--wr-color-on-surface);font-size:1rem;text-align:center;padding:var(--wr-space-lg) 1.625rem;border-radius:var(--wr-star-border-radius);z-index:1}@keyframes wr-star-border-bottom{0%{transform:translate(0);opacity:1}to{transform:translate(-100%);opacity:0}}@keyframes wr-star-border-top{0%{transform:translate(0);opacity:1}to{transform:translate(100%);opacity:0}}@media(prefers-reduced-motion:reduce){.wr-star-border__ray{animation:none!important;opacity:0!important}}\n"],
|
|
94
94
|
encapsulation: i0.ViewEncapsulation.None
|
|
95
95
|
});
|
|
96
96
|
};
|
|
97
97
|
i0.ɵɵngDeclareClassMetadata({
|
|
98
98
|
minVersion: "12.0.0",
|
|
99
|
-
version: "22.2.
|
|
99
|
+
version: "22.2.4",
|
|
100
100
|
ngImport: i0,
|
|
101
101
|
type: WrStarBorder,
|
|
102
102
|
decorators: [{
|
|
@@ -106,12 +106,12 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
106
106
|
encapsulation: ViewEncapsulation.None,
|
|
107
107
|
host: {
|
|
108
108
|
"[class]": "classes()",
|
|
109
|
-
"[style.--wr-star-border-color]": "color()",
|
|
109
|
+
"[style.--wr-star-border-input-color]": "color()",
|
|
110
110
|
"[style.--wr-star-border-speed]": "speed() + \"s\"",
|
|
111
111
|
"[style.padding]": "thickness() + \"px 0\""
|
|
112
112
|
},
|
|
113
113
|
template: "<span class=\"wr-star-border__ray wr-star-border__ray--bottom\" aria-hidden=\"true\"></span>\n<span class=\"wr-star-border__ray wr-star-border__ray--top\" aria-hidden=\"true\"></span>\n<span class=\"wr-star-border__inner\"><ng-content /></span>\n",
|
|
114
|
-
styles: [".wr-star-border{--wr-star-border-color: light-dark(var(--wr-color-primary), #ffffff);--wr-star-border-speed: 6s;--wr-star-border-radius: 20px;--wr-star-border-opacity: 1;display:inline-block;position:relative;border:0;background:transparent;border-radius:var(--wr-star-border-radius);overflow:hidden;cursor:inherit}.wr-star-border__ray{position:absolute;display:block;width:300%;height:50%;opacity:var(--wr-star-border-opacity);border-radius:50%;background:radial-gradient(circle,var(--wr-star-border-color),light-dark(color-mix(in srgb,var(--wr-star-border-color) 37.5%,transparent),transparent) 10%,transparent 16%);animation:wr-star-border-bottom var(--wr-star-border-speed) linear infinite alternate;z-index:0}.wr-star-border__ray--bottom{bottom:-12px;right:-250%}.wr-star-border__ray--top{top:-12px;left:-250%;animation-name:wr-star-border-top}.wr-star-border--single .wr-star-border__ray--top{display:none}.wr-star-border--hover .wr-star-border__ray{opacity:0;animation:none}.wr-star-border--hover:hover .wr-star-border__ray{animation:wr-star-border-bottom var(--wr-star-border-speed) linear infinite alternate}.wr-star-border--hover:hover .wr-star-border__ray--top{animation-name:wr-star-border-top}.wr-star-border__inner{position:relative;display:block;border:1px solid var(--wr-border-base);background:var(--wr-color-surface);color:var(--wr-color-on-surface);font-size:1rem;text-align:center;padding:var(--wr-space-lg) 1.625rem;border-radius:var(--wr-star-border-radius);z-index:1}@keyframes wr-star-border-bottom{0%{transform:translate(0);opacity:1}to{transform:translate(-100%);opacity:0}}@keyframes wr-star-border-top{0%{transform:translate(0);opacity:1}to{transform:translate(100%);opacity:0}}@media(prefers-reduced-motion:reduce){.wr-star-border__ray{animation:none!important;opacity:0!important}}\n"]
|
|
114
|
+
styles: [".wr-star-border{--wr-star-border-color: light-dark(var(--wr-color-primary), #ffffff);--wr-star-border-input-color: initial;--wr-star-border-speed: 6s;--wr-star-border-radius: 20px;--wr-star-border-opacity: 1;display:inline-block;position:relative;border:0;background:transparent;border-radius:var(--wr-star-border-radius);overflow:hidden;cursor:inherit}.wr-star-border__ray{position:absolute;display:block;width:300%;height:50%;opacity:var(--wr-star-border-opacity);border-radius:50%;background:radial-gradient(circle,var(--wr-star-border-input-color, var(--wr-star-border-color)),light-dark(color-mix(in srgb,var(--wr-star-border-input-color, var(--wr-star-border-color)) 37.5%,transparent),transparent) 10%,transparent 16%);animation:wr-star-border-bottom var(--wr-star-border-speed) linear infinite alternate;z-index:0}.wr-star-border__ray--bottom{bottom:-12px;right:-250%}.wr-star-border__ray--top{top:-12px;left:-250%;animation-name:wr-star-border-top}.wr-star-border--single .wr-star-border__ray--top{display:none}.wr-star-border--hover .wr-star-border__ray{opacity:0;animation:none}.wr-star-border--hover:hover .wr-star-border__ray{animation:wr-star-border-bottom var(--wr-star-border-speed) linear infinite alternate}.wr-star-border--hover:hover .wr-star-border__ray--top{animation-name:wr-star-border-top}.wr-star-border__inner{position:relative;display:block;border:1px solid var(--wr-border-base);background:var(--wr-color-surface);color:var(--wr-color-on-surface);font-size:1rem;text-align:center;padding:var(--wr-space-lg) 1.625rem;border-radius:var(--wr-star-border-radius);z-index:1}@keyframes wr-star-border-bottom{0%{transform:translate(0);opacity:1}to{transform:translate(-100%);opacity:0}}@keyframes wr-star-border-top{0%{transform:translate(0);opacity:1}to{transform:translate(100%);opacity:0}}@media(prefers-reduced-motion:reduce){.wr-star-border__ray{animation:none!important;opacity:0!important}}\n"]
|
|
115
115
|
}]
|
|
116
116
|
}],
|
|
117
117
|
propDecorators: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-bits-star-border.mjs","names":[],"sources":["../../../projects/lib/bits/star-border/star-border.ts","../../../projects/lib/bits/star-border/star-border.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 * Angular adaptation of the StarBorder effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/animations/star-border\n *\n * Extends the original with `mode` (always animating vs hover-only) and\n * `rays` (mirrored pair vs a single ray).\n */\n\nimport { Component, ViewEncapsulation, input } from '@angular/core';\n\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrStarBorderMode, WrStarBorderRays } from './types';\n\n/**\n * Star border — radial \"comet\" rays orbiting the top and bottom edges of\n * a rounded container. Project any content; it sits on a themed inner\n * panel above the rays.\n *\n * Works as an element or as an attribute on buttons / links.\n *\n * @example\n * ```html\n * <wr-star-border>Star border</wr-star-border>\n * <button wr-star-border mode=\"hover\" rays=\"single\">Hover me</button>\n * ```\n *\n * @see https://ngwr.dev/bits/star-border\n */\n@Component({\n selector: 'wr-star-border, [wr-star-border]',\n templateUrl: './star-border.html',\n styleUrl: './star-border.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[style.--wr-star-border-color]': 'color()',\n '[style.--wr-star-border-speed]': 'speed() + \"s\"',\n '[style.padding]': 'thickness() + \"px 0\"',\n },\n})\nexport class WrStarBorder {\n /** Ray colour. When unset, the theme decides: primary on light, white on dark. */\n readonly color = input<string | null>(null);\n\n /** Seconds per ray sweep. @default 6 */\n readonly speed = input(6, { transform: numAttr(6) });\n\n /** Vertical bleed of the rays past the inner panel, in px. @default 1 */\n readonly thickness = input(1, { transform: numAttr(1) });\n\n /** `'infinite'` animates always; `'hover'` only while hovered. @default 'infinite' */\n readonly mode = input<WrStarBorderMode>('infinite');\n\n /** `'mirror'` runs rays along both edges; `'single'` only the bottom. @default 'mirror' */\n readonly rays = input<WrStarBorderRays>('mirror');\n\n protected readonly classes = (): string => {\n const parts = ['wr-star-border'];\n if (this.mode() === 'hover') parts.push('wr-star-border--hover');\n if (this.rays() === 'single') parts.push('wr-star-border--single');\n return parts.join(' ');\n };\n}\n\nexport type { WrStarBorderMode, WrStarBorderRays } from './types';\n","<span class=\"wr-star-border__ray wr-star-border__ray--bottom\" aria-hidden=\"true\"></span>\n<span class=\"wr-star-border__ray wr-star-border__ray--top\" aria-hidden=\"true\"></span>\n<span class=\"wr-star-border__inner\"><ng-content /></span>\n"],"mappings":";;;;;;;;;;;;;;;AA8CA,IAAa,eAAb,MAAa,aAAY;CAEd,QAAQ,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAGlC,QAAQ,MAAM,GAAC;EAAA,GAAA,YAAA,EAAA,WAAA,QAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,CAAC;CAAC,CAAA;CAGxC,YAAY,MAAM,GAAC;EAAA,GAAA,YAAA,EAAA,WAAA,YAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,CAAC;CAAC,CAAA;CAG5C,OAAO,MAAwB,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAG1C,OAAO,MAAwB,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAE9B,gBAAuB;EACxC,MAAM,QAAQ,CAAC,gBAAgB;EAC/B,IAAI,KAAK,KAAI,MAAO,SAAS,MAAM,KAAK,uBAAuB;EAC/D,IAAI,KAAK,KAAI,MAAO,UAAU,MAAM,KAAK,wBAAwB;EACjE,OAAO,MAAM,KAAK,GAAG;CACvB;CArBW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAY,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAZ,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAY,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA,EAAA,YAAA;GAAA,SAAA;GAAA,
|
|
1
|
+
{"version":3,"file":"ngwr-bits-star-border.mjs","names":[],"sources":["../../../projects/lib/bits/star-border/star-border.ts","../../../projects/lib/bits/star-border/star-border.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 * Angular adaptation of the StarBorder effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/animations/star-border\n *\n * Extends the original with `mode` (always animating vs hover-only) and\n * `rays` (mirrored pair vs a single ray).\n */\n\nimport { Component, ViewEncapsulation, input } from '@angular/core';\n\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrStarBorderMode, WrStarBorderRays } from './types';\n\n/**\n * Star border — radial \"comet\" rays orbiting the top and bottom edges of\n * a rounded container. Project any content; it sits on a themed inner\n * panel above the rays.\n *\n * Works as an element or as an attribute on buttons / links.\n *\n * @example\n * ```html\n * <wr-star-border>Star border</wr-star-border>\n * <button wr-star-border mode=\"hover\" rays=\"single\">Hover me</button>\n * ```\n *\n * @see https://ngwr.dev/bits/star-border\n */\n@Component({\n selector: 'wr-star-border, [wr-star-border]',\n templateUrl: './star-border.html',\n styleUrl: './star-border.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n '[style.--wr-star-border-input-color]': 'color()',\n '[style.--wr-star-border-speed]': 'speed() + \"s\"',\n '[style.padding]': 'thickness() + \"px 0\"',\n },\n})\nexport class WrStarBorder {\n /** Ray colour. When unset, the theme decides: primary on light, white on dark. */\n readonly color = input<string | null>(null);\n\n /** Seconds per ray sweep. @default 6 */\n readonly speed = input(6, { transform: numAttr(6) });\n\n /** Vertical bleed of the rays past the inner panel, in px. @default 1 */\n readonly thickness = input(1, { transform: numAttr(1) });\n\n /** `'infinite'` animates always; `'hover'` only while hovered. @default 'infinite' */\n readonly mode = input<WrStarBorderMode>('infinite');\n\n /** `'mirror'` runs rays along both edges; `'single'` only the bottom. @default 'mirror' */\n readonly rays = input<WrStarBorderRays>('mirror');\n\n protected readonly classes = (): string => {\n const parts = ['wr-star-border'];\n if (this.mode() === 'hover') parts.push('wr-star-border--hover');\n if (this.rays() === 'single') parts.push('wr-star-border--single');\n return parts.join(' ');\n };\n}\n\nexport type { WrStarBorderMode, WrStarBorderRays } from './types';\n","<span class=\"wr-star-border__ray wr-star-border__ray--bottom\" aria-hidden=\"true\"></span>\n<span class=\"wr-star-border__ray wr-star-border__ray--top\" aria-hidden=\"true\"></span>\n<span class=\"wr-star-border__inner\"><ng-content /></span>\n"],"mappings":";;;;;;;;;;;;;;;AA8CA,IAAa,eAAb,MAAa,aAAY;CAEd,QAAQ,MAAqB,MAAK,GAAA,YAAA,CAAA,EAAA,WAAA,QAAA,CAAA,+BAAA,CAAA,CAAA;CAGlC,QAAQ,MAAM,GAAC;EAAA,GAAA,YAAA,EAAA,WAAA,QAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,CAAC;CAAC,CAAA;CAGxC,YAAY,MAAM,GAAC;EAAA,GAAA,YAAA,EAAA,WAAA,YAAA,+BAAA,CAAA;EAAI,WAAW,QAAQ,CAAC;CAAC,CAAA;CAG5C,OAAO,MAAwB,YAAW,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAG1C,OAAO,MAAwB,UAAS,GAAA,YAAA,CAAA,EAAA,WAAA,OAAA,CAAA,+BAAA,CAAA,CAAA;CAE9B,gBAAuB;EACxC,MAAM,QAAQ,CAAC,gBAAgB;EAC/B,IAAI,KAAK,KAAI,MAAO,SAAS,MAAM,KAAK,uBAAuB;EAC/D,IAAI,KAAK,KAAI,MAAO,UAAU,MAAM,KAAK,wBAAwB;EACjE,OAAO,MAAM,KAAK,GAAG;CACvB;CArBW,OAAA,OAAA,GAAA,mBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,UAAA;EAAA,MAAA;EAAY,MAAA,CAAA;EAAA,QAAA,GAAA,gBAAA;CAAA,CAAA;CAAZ,OAAA,OAAA,GAAA,qBAAA;EAAA,YAAA;EAAA,SAAA;EAAA,MAAA;EAAY,cAAA;EAAA,UAAA;EAAA,QAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,OAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,WAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;GAAA,MAAA;IAAA,mBAAA;IAAA,YAAA;IAAA,UAAA;IAAA,YAAA;IAAA,mBAAA;GAAA;EAAA;EAAA,MAAA,EAAA,YAAA;GAAA,SAAA;GAAA,sCAAA;GAAA,gCAAA;GAAA,iBAAA;EAAA,EAAA;EAAA,UAAA;EAAA,UC9CzB;EAGA,QAAA,CAAA,m4DAAA;EAAA,eAAA,GAAA,kBAAA;CAAA,CAAA;;AD2Ca,GAAA,yBAAA;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAY,YAAA,CAAA;EAZxB,MAAA;EACW,MAAA,CAAA;GAAA,UAAA;GAAkC,eAG7B,kBAAkB;GAAI,MAC/B;IACJ,WAAW;IACX,wCAAwC;IACxC,kCAAkC;IAClC,mBAAmB;GACpB;GAAA,UAAA;GAAA,QAAA,CAAA,m4DAAA;EAAA,CAAA"}
|
|
@@ -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 { coerceBooleanProperty } from "@angular/cdk/coercion";
|
|
4
4
|
import { DOCUMENT as DOCUMENT$1, isPlatformBrowser } from "@angular/common";
|
|
5
5
|
import { clamp, numAttr, resolveCssColor, watchColorScheme } from "ngwr/utils";
|
|
@@ -63,7 +63,7 @@ var WrThreads = class WrThreads {
|
|
|
63
63
|
bandShare = computed(() => this.count() > 1 ? clamp(this.spread(), 0, 1) : 0, ...ngDevMode ? [{ debugName: "bandShare" }] : /* istanbul ignore next */ []);
|
|
64
64
|
customColor = computed(() => {
|
|
65
65
|
const color = this.color().trim();
|
|
66
|
-
return color === "" || color
|
|
66
|
+
return color === "" || color === THEME_COLOR ? null : color;
|
|
67
67
|
}, ...ngDevMode ? [{ debugName: "customColor" }] : /* istanbul ignore next */ []);
|
|
68
68
|
moving = computed(() => !this.platform.prefersReducedMotion() && (this.speed() !== 0 || this.followPointer()), ...ngDevMode ? [{ debugName: "moving" }] : /* istanbul ignore next */ []);
|
|
69
69
|
painted = signal(false, ...ngDevMode ? [{ debugName: "painted" }] : /* istanbul ignore next */ []);
|
|
@@ -96,10 +96,11 @@ var WrThreads = class WrThreads {
|
|
|
96
96
|
dirty = true;
|
|
97
97
|
booted = signal(false, ...ngDevMode ? [{ debugName: "booted" }] : /* istanbul ignore next */ []);
|
|
98
98
|
onScreen = signal(true, ...ngDevMode ? [{ debugName: "onScreen" }] : /* istanbul ignore next */ []);
|
|
99
|
+
hasBox = signal(false, ...ngDevMode ? [{ debugName: "hasBox" }] : /* istanbul ignore next */ []);
|
|
99
100
|
constructor() {
|
|
100
101
|
if (!this.isBrowser) return;
|
|
101
102
|
afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));
|
|
102
|
-
|
|
103
|
+
afterRenderEffect(() => {
|
|
103
104
|
if (!this.booted()) return;
|
|
104
105
|
const moving = this.moving();
|
|
105
106
|
this.count();
|
|
@@ -113,7 +114,7 @@ var WrThreads = class WrThreads {
|
|
|
113
114
|
if (this.booted()) this.repaint(this.moving());
|
|
114
115
|
});
|
|
115
116
|
effect((onCleanup) => {
|
|
116
|
-
if (!this.booted() || !this.onScreen() || !this.moving()) return;
|
|
117
|
+
if (!this.booted() || !this.onScreen() || !this.hasBox() || !this.moving()) return;
|
|
117
118
|
const follow = this.followPointer();
|
|
118
119
|
onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.animate(follow))));
|
|
119
120
|
});
|
|
@@ -131,7 +132,10 @@ var WrThreads = class WrThreads {
|
|
|
131
132
|
window.addEventListener("resize", onResize);
|
|
132
133
|
const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(onResize);
|
|
133
134
|
ro?.observe(host);
|
|
134
|
-
const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) =>
|
|
135
|
+
const io = typeof IntersectionObserver === "undefined" ? null : new IntersectionObserver((entries) => {
|
|
136
|
+
const { isIntersecting, intersectionRatio } = entries[entries.length - 1];
|
|
137
|
+
this.onScreen.set(isIntersecting && intersectionRatio > 0);
|
|
138
|
+
}, { threshold: [0, 1e-9] });
|
|
135
139
|
io?.observe(host);
|
|
136
140
|
this.destroyRef.onDestroy(() => {
|
|
137
141
|
window.removeEventListener("resize", onResize);
|
|
@@ -207,6 +211,7 @@ var WrThreads = class WrThreads {
|
|
|
207
211
|
width,
|
|
208
212
|
height
|
|
209
213
|
};
|
|
214
|
+
this.hasBox.set(width > 0 && height > 0);
|
|
210
215
|
const canvas = this.canvasRef().nativeElement;
|
|
211
216
|
const area = width * height;
|
|
212
217
|
const budget = area > 0 ? Math.sqrt(MAX_CANVAS_PIXELS / area) : 2;
|
|
@@ -299,7 +304,7 @@ var WrThreads = class WrThreads {
|
|
|
299
304
|
}
|
|
300
305
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
301
306
|
minVersion: "12.0.0",
|
|
302
|
-
version: "22.2.
|
|
307
|
+
version: "22.2.4",
|
|
303
308
|
ngImport: i0,
|
|
304
309
|
type: WrThreads,
|
|
305
310
|
deps: [],
|
|
@@ -307,7 +312,7 @@ var WrThreads = class WrThreads {
|
|
|
307
312
|
});
|
|
308
313
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
309
314
|
minVersion: "17.2.0",
|
|
310
|
-
version: "22.2.
|
|
315
|
+
version: "22.2.4",
|
|
311
316
|
type: WrThreads,
|
|
312
317
|
isStandalone: true,
|
|
313
318
|
selector: "wr-threads",
|
|
@@ -365,7 +370,7 @@ var WrThreads = class WrThreads {
|
|
|
365
370
|
host: {
|
|
366
371
|
properties: {
|
|
367
372
|
"class.wr-threads--painted": "painted()",
|
|
368
|
-
"style.--wr-threads-color": "customColor()",
|
|
373
|
+
"style.--wr-threads-input-color": "customColor()",
|
|
369
374
|
"style.--wr-threads-count": "count()",
|
|
370
375
|
"style.--wr-threads-spread": "bandShare()",
|
|
371
376
|
"style.--wr-threads-line-width.px": "strokeWidth()"
|
|
@@ -382,13 +387,13 @@ var WrThreads = class WrThreads {
|
|
|
382
387
|
ngImport: i0,
|
|
383
388
|
template: "<canvas #canvas class=\"wr-threads__canvas\" aria-hidden=\"true\"></canvas>",
|
|
384
389
|
isInline: true,
|
|
385
|
-
styles: [".wr-threads{--wr-threads-color: light-dark( color-mix(in srgb, var(--wr-color-primary) 60%, transparent), color-mix(in srgb, var(--wr-color-primary-lighter) 60%, transparent) );--wr-threads-count: 18;--wr-threads-spread: .4;--wr-threads-line-width: 1px;position:absolute;inset:0;z-index:0;display:block;margin:0;padding:0;overflow:hidden;pointer-events:none}.wr-threads:before{content:\"\";position:absolute;inset:0;background-image:repeating-linear-gradient(to bottom,var(--wr-threads-color) 0 var(--wr-threads-line-width),transparent var(--wr-threads-line-width) calc((100% - var(--wr-threads-line-width)) / max(1,var(--wr-threads-count) - 1)));background-position:center;background-repeat:no-repeat;background-size:100% calc(var(--wr-threads-spread) * 100% + var(--wr-threads-line-width));-webkit-mask-image:linear-gradient(to bottom,#0006,#000,#0006);mask-image:linear-gradient(to bottom,#0006,#000,#0006);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:100% calc(var(--wr-threads-spread) * 100% + var(--wr-threads-line-width));mask-size:100% calc(var(--wr-threads-spread) * 100% + var(--wr-threads-line-width))}.wr-threads--painted:before{content:none}.wr-threads__canvas{display:block;width:100%;height:100%}\n"],
|
|
390
|
+
styles: [".wr-threads{--wr-threads-color: light-dark( color-mix(in srgb, var(--wr-color-primary) 60%, transparent), color-mix(in srgb, var(--wr-color-primary-lighter) 60%, transparent) );--wr-threads-count: 18;--wr-threads-spread: .4;--wr-threads-line-width: 1px;position:absolute;inset:0;z-index:0;display:block;margin:0;padding:0;overflow:hidden;pointer-events:none}.wr-threads:before{content:\"\";position:absolute;inset:0;background-image:repeating-linear-gradient(to bottom,var(--wr-threads-input-color, var(--wr-threads-color)) 0 var(--wr-threads-line-width),transparent var(--wr-threads-line-width) calc((100% - var(--wr-threads-line-width)) / max(1,var(--wr-threads-count) - 1)));background-position:center;background-repeat:no-repeat;background-size:100% calc(var(--wr-threads-spread) * 100% + var(--wr-threads-line-width));-webkit-mask-image:linear-gradient(to bottom,#0006,#000,#0006);mask-image:linear-gradient(to bottom,#0006,#000,#0006);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:100% calc(var(--wr-threads-spread) * 100% + var(--wr-threads-line-width));mask-size:100% calc(var(--wr-threads-spread) * 100% + var(--wr-threads-line-width))}.wr-threads--painted:before{content:none}.wr-threads__canvas{display:block;width:100%;height:100%}@media(forced-colors:active){.wr-threads__canvas{forced-color-adjust:none}}\n"],
|
|
386
391
|
encapsulation: i0.ViewEncapsulation.None
|
|
387
392
|
});
|
|
388
393
|
};
|
|
389
394
|
i0.ɵɵngDeclareClassMetadata({
|
|
390
395
|
minVersion: "12.0.0",
|
|
391
|
-
version: "22.2.
|
|
396
|
+
version: "22.2.4",
|
|
392
397
|
ngImport: i0,
|
|
393
398
|
type: WrThreads,
|
|
394
399
|
decorators: [{
|
|
@@ -400,12 +405,12 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
400
405
|
host: {
|
|
401
406
|
class: "wr-threads",
|
|
402
407
|
"[class.wr-threads--painted]": "painted()",
|
|
403
|
-
"[style.--wr-threads-color]": "customColor()",
|
|
408
|
+
"[style.--wr-threads-input-color]": "customColor()",
|
|
404
409
|
"[style.--wr-threads-count]": "count()",
|
|
405
410
|
"[style.--wr-threads-spread]": "bandShare()",
|
|
406
411
|
"[style.--wr-threads-line-width.px]": "strokeWidth()"
|
|
407
412
|
},
|
|
408
|
-
styles: [".wr-threads{--wr-threads-color: light-dark( color-mix(in srgb, var(--wr-color-primary) 60%, transparent), color-mix(in srgb, var(--wr-color-primary-lighter) 60%, transparent) );--wr-threads-count: 18;--wr-threads-spread: .4;--wr-threads-line-width: 1px;position:absolute;inset:0;z-index:0;display:block;margin:0;padding:0;overflow:hidden;pointer-events:none}.wr-threads:before{content:\"\";position:absolute;inset:0;background-image:repeating-linear-gradient(to bottom,var(--wr-threads-color) 0 var(--wr-threads-line-width),transparent var(--wr-threads-line-width) calc((100% - var(--wr-threads-line-width)) / max(1,var(--wr-threads-count) - 1)));background-position:center;background-repeat:no-repeat;background-size:100% calc(var(--wr-threads-spread) * 100% + var(--wr-threads-line-width));-webkit-mask-image:linear-gradient(to bottom,#0006,#000,#0006);mask-image:linear-gradient(to bottom,#0006,#000,#0006);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:100% calc(var(--wr-threads-spread) * 100% + var(--wr-threads-line-width));mask-size:100% calc(var(--wr-threads-spread) * 100% + var(--wr-threads-line-width))}.wr-threads--painted:before{content:none}.wr-threads__canvas{display:block;width:100%;height:100%}\n"]
|
|
413
|
+
styles: [".wr-threads{--wr-threads-color: light-dark( color-mix(in srgb, var(--wr-color-primary) 60%, transparent), color-mix(in srgb, var(--wr-color-primary-lighter) 60%, transparent) );--wr-threads-count: 18;--wr-threads-spread: .4;--wr-threads-line-width: 1px;position:absolute;inset:0;z-index:0;display:block;margin:0;padding:0;overflow:hidden;pointer-events:none}.wr-threads:before{content:\"\";position:absolute;inset:0;background-image:repeating-linear-gradient(to bottom,var(--wr-threads-input-color, var(--wr-threads-color)) 0 var(--wr-threads-line-width),transparent var(--wr-threads-line-width) calc((100% - var(--wr-threads-line-width)) / max(1,var(--wr-threads-count) - 1)));background-position:center;background-repeat:no-repeat;background-size:100% calc(var(--wr-threads-spread) * 100% + var(--wr-threads-line-width));-webkit-mask-image:linear-gradient(to bottom,#0006,#000,#0006);mask-image:linear-gradient(to bottom,#0006,#000,#0006);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:100% calc(var(--wr-threads-spread) * 100% + var(--wr-threads-line-width));mask-size:100% calc(var(--wr-threads-spread) * 100% + var(--wr-threads-line-width))}.wr-threads--painted:before{content:none}.wr-threads__canvas{display:block;width:100%;height:100%}@media(forced-colors:active){.wr-threads__canvas{forced-color-adjust:none}}\n"]
|
|
409
414
|
}]
|
|
410
415
|
}],
|
|
411
416
|
ctorParameters: () => [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-bits-threads.mjs","names":["DOCUMENT"],"sources":["../../../projects/lib/bits/threads/threads.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 Threads background idea on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/threads), written for ngwr from\n * scratch: the original is a WebGL shader, this is a 2D canvas, and the motion,\n * the inputs and the names are this library's own.\n *\n * A 2D canvas rather than WebGL, deliberately. The picture is a few dozen thin\n * strokes, which a 2D context antialiases on its own; it boots with no shader to\n * compile, and it still draws on the machines that blocklist WebGL2.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { DOCUMENT, 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 { clamp, numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\n/** The theme's hook. A `color` input left at this, or emptied, follows the theme. */\nconst THEME_COLOR = 'var(--wr-threads-color)';\n\n/** What a stroke falls back to when no colour resolves: the hook's light value, flattened. */\nconst FALLBACK_COLOR = 'rgba(57, 105, 226, 0.6)';\n\n/** The most threads drawn. Past this the band is a fill, not strands, and the frame cost keeps climbing. */\nconst MAX_LINES = 100;\n\n/** The thinnest stroke drawn, in CSS px. A canvas ignores a zero or negative `lineWidth` and keeps the last one. */\nconst MIN_LINE_WIDTH = 0.25;\n\n/** Horizontal distance between the points a thread is curved through, in CSS px. */\nconst SAMPLE_STEP = 12;\n\n/**\n * The most bitmap pixels the canvas is backed with — the same budget `wr-waves`\n * keeps, for the same reason: the field is redrawn every frame, so the backing\n * store is a per-frame cost, and a wide retina window steps down toward 1x\n * instead of growing a 3456-pixel bitmap.\n */\nconst MAX_CANVAS_PIXELS = 2_500_000;\n\n/**\n * The longest step the current takes in one frame, in ms. A frame that arrives\n * late — a tab coming back, a long task — advances the threads by this much and\n * no more, so they never jump to a shape the reader did not see them reach.\n */\nconst MAX_FRAME_MS = 50;\n\n/** How much fainter the outermost threads are than the middle one, 0..1. */\nconst EDGE_FADE = 0.6;\n\n/** Share of the distance to the pointer a thread right under it bends. */\nconst POINTER_PULL = 0.55;\n\n/** Per-frame easing of the pointer's position and of its pull. */\nconst POINTER_FOLLOW = 0.12;\nconst POINTER_FADE = 0.06;\n\nconst TAU = Math.PI * 2;\n\n/** The golden angle, in radians: spaces each thread's ripple phase so no two neighbours move in step. */\nconst GOLDEN_ANGLE = 2.399963;\n\n/**\n * Threads background — thin lines flowing across the box like strands in a\n * current. The bundle meanders as one, breathes wider and narrower along its\n * length, and every strand ripples on its own phase, so neighbours cross and\n * part. With `followPointer` the strands near the pointer bend gently toward\n * it. Fills its nearest positioned ancestor: put it first in a\n * `position: relative` box and give the content after it a position of its own.\n *\n * Decorative only: the canvas is `aria-hidden`, the host has no role and\n * nothing in it takes focus or a click.\n *\n * Under `prefers-reduced-motion` one still frame is drawn and nothing follows —\n * no current, no pointer listeners, no frame loop. The frame is drawn again when\n * an input changes, the box resizes or the colour scheme switches, and the\n * preference is read live, so switching it while the page is open takes effect\n * at once.\n *\n * At `speed` 0 with `followPointer` off nothing can move either, so the same\n * still frame is drawn — held where the current was — and no loop runs. At\n * `speed` 0 with the pointer followed, the loop runs and draws only while the\n * pointer is bending something.\n *\n * Off screen the loop stops asking for frames and resumes where it left off.\n *\n * The colour is theme-aware by default — `--wr-threads-color`, a soft wash of\n * the primary intent — and `[color]` takes any CSS colour instead, a `var()`\n * token or a `light-dark()` pair included. A canvas reads neither, so either is\n * resolved through the cascade before it reaches a stroke.\n *\n * Until the canvas has drawn — the seconds before hydration on a prerendered\n * page — the stylesheet paints the threads at rest as straight lines, at the\n * same count, colour, width and spread, and `.wr-threads--painted` retires them.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 20rem\">\n * <wr-threads />\n * <h1 style=\"position: relative\">Hero copy above the threads</h1>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/threads\n */\n@Component({\n selector: 'wr-threads',\n template: '<canvas #canvas class=\"wr-threads__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './threads.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-threads',\n '[class.wr-threads--painted]': 'painted()',\n // Feed the CSS stand-in (see styles/_rules.scss) so the straight lines it\n // draws before hydration sit where the canvas will put the threads.\n '[style.--wr-threads-color]': 'customColor()',\n '[style.--wr-threads-count]': 'count()',\n '[style.--wr-threads-spread]': 'bandShare()',\n '[style.--wr-threads-line-width.px]': 'strokeWidth()',\n },\n})\nexport class WrThreads {\n /** How many threads, rounded and kept between 1 and 100. @default 18 */\n readonly lineCount = input(18, { transform: numAttr(18) });\n\n /**\n * Thread colour, any CSS colour — a `var()` token or a `light-dark()` pair\n * included. Emptied, it follows the theme. @default 'var(--wr-threads-color)'\n */\n readonly color = input(THEME_COLOR);\n\n /** How far the bundle meanders up and down, in px. @default 40 */\n readonly amplitude = input(40, { transform: numAttr(40) });\n\n /** Speed of the current; 0 holds it, a negative value reverses it. @default 1 */\n readonly speed = input(1, { transform: numAttr(1) });\n\n /** Stroke width of each thread, in px. @default 1 */\n readonly lineWidth = input(1, { transform: numAttr(1) });\n\n /** Share of the box height the threads fan across at their widest, 0..1. @default 0.4 */\n readonly spread = input(0.4, { transform: numAttr(0.4) });\n\n /** Whether the threads near the pointer bend toward it. @default true */\n readonly followPointer = input(true, { transform: coerceBooleanProperty });\n\n /** The thread count actually drawn. */\n protected readonly count = computed(() => clamp(Math.round(this.lineCount()), 1, MAX_LINES));\n\n /** The stroke width actually drawn. */\n protected readonly strokeWidth = computed(() => Math.max(MIN_LINE_WIDTH, this.lineWidth()));\n\n /** The band's share of the height. One thread has no band: it runs through the middle. */\n protected readonly bandShare = computed(() => (this.count() > 1 ? clamp(this.spread(), 0, 1) : 0));\n\n /**\n * A colour the author chose, written onto the host's own hook so the stand-in\n * draws in it too; `null` while the input is the theme's, which removes the\n * declaration and leaves the stylesheet's value in charge. Also `null` for an\n * expression built ON the hook — `color-mix(in srgb, var(--wr-threads-color)\n * 50%, transparent)` — which, written onto the hook itself, would be a cycle\n * and invalidate it: the canvas resolves such an expression against the theme's\n * value, and the stand-in keeps that value.\n */\n protected readonly customColor = computed(() => {\n const color = this.color().trim();\n return color === '' || color.includes('--wr-threads-color') ? null : color;\n });\n\n /**\n * Whether anything can change between two frames: never under\n * `prefers-reduced-motion`, read live, and not at `speed` 0 with no pointer to\n * follow. While it is `false` there is no loop, only a still frame.\n */\n private readonly moving = computed(\n () => !this.platform.prefersReducedMotion() && (this.speed() !== 0 || this.followPointer())\n );\n\n /**\n * Flips once the canvas has drawn, which retires the CSS stand-in. Set inside\n * the draw rather than at the end of the boot, so it can never switch the\n * stand-in off over a canvas with nothing on it.\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 doc = inject(DOCUMENT);\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 private bounds = { width: 0, height: 0 };\n private rafId = 0;\n\n /** Seconds of current so far, already scaled by `speed`. Kept across a parked loop, so it resumes in place. */\n private phase = 0;\n\n /** Eased pointer position in host px (`x`, `y`), where it is heading (`tx`, `ty`), and how hard it pulls, 0..1. */\n private readonly pointer = { x: 0, y: 0, tx: 0, ty: 0, inside: false, pull: 0 };\n\n /**\n * Per-sample buffers, reused frame to frame rather than allocated per thread:\n * one thread's heights, and the three terms every thread shares at a sample —\n * the bundle's meander, how wide the band is breathing, and the pointer's bell\n * along `x` — computed once per frame instead of once per thread.\n */\n private ys = new Float64Array(0);\n private meanders = new Float64Array(0);\n private breaths = new Float64Array(0);\n private alongs = new Float64Array(0);\n\n /**\n * Whether the picture changed since the loop last drew it — an input, the\n * colour scheme, a restart. The loop skips the frames that would only repeat\n * the last one (a held current, no pull), and this is what makes it draw again.\n */\n private dirty = true;\n\n /** Flips once `boot()` has a context — everything that draws waits for it. */\n private readonly booted = signal(false);\n\n /** Whether the host is in the viewport; stays `true` without an observer. */\n private readonly onScreen = signal(true);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\n\n // A still frame stays as it was drawn, so with nothing moving every input\n // that changes the picture paints it again — and so does the moment motion\n // stops. A running loop is only told the picture changed: it skips the\n // frames that would repeat the last one, and draws the next one again.\n effect(() => {\n if (!this.booted()) return;\n const moving = this.moving();\n this.count();\n this.bandShare();\n this.amplitude();\n this.strokeWidth();\n this.color();\n untracked(() => this.repaint(moving));\n });\n\n // The same for the colour scheme: a stroke keeps the colour it was resolved\n // in, and nothing else redraws a still frame or a held loop.\n watchColorScheme(() => {\n if (this.booted()) this.repaint(this.moving());\n });\n\n // The current runs only while there is something to see: never while\n // nothing can move (see `moving`), and not while the host is off screen.\n // Toggling `followPointer` restarts it with or without the pointer listeners.\n effect(onCleanup => {\n if (!this.booted() || !this.onScreen() || !this.moving()) return;\n const follow = this.followPointer();\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.animate(follow))));\n });\n }\n\n // Setup\n\n private boot(): void {\n const host = this.host.nativeElement;\n this.ctx = this.canvasRef().nativeElement.getContext('2d');\n if (!this.ctx) return;\n\n this.setSize();\n\n // Resizing the bitmap clears it, so the frame is drawn again right away\n // rather than left blank until the next one: a ResizeObserver callback runs\n // after that frame's animation callbacks and before its paint.\n const onResize = (): void => {\n this.setSize();\n if (this.moving()) this.draw(this.phase);\n else this.drawStill();\n };\n window.addEventListener('resize', onResize);\n const ro = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(onResize);\n ro?.observe(host);\n\n // Off screen the loop stops asking for frames: a background redrawn out of\n // sight still costs the browser a frame every frame. Without an observer\n // (jsdom) the host simply counts as visible.\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(entries => this.onScreen.set(entries[entries.length - 1].isIntersecting));\n io?.observe(host);\n\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('resize', onResize);\n ro?.disconnect();\n io?.disconnect();\n });\n this.booted.set(true);\n }\n\n /** Start the current, and the pointer listeners when asked; returns what stops both. */\n private animate(follow: boolean): () => void {\n const stopPointer = follow ? this.listenToPointer() : null;\n let last = -1;\n // A frame already handed to the browser can still arrive after the stop —\n // a held or polyfilled scheduler does not always honour the cancel — and a\n // late one must not draw, nor schedule a loop nobody will stop again.\n let stopped = false;\n // The canvas holds whatever was drawn before the loop stopped, which may not\n // be the picture now: the first frame always draws.\n this.dirty = true;\n const tick = (now: number): void => {\n if (stopped) return;\n const phase = this.phase;\n if (last >= 0) this.phase += (clamp(now - last, 0, MAX_FRAME_MS) / 1000) * this.speed();\n last = now;\n const pulled = this.pointer.pull > 0;\n this.easePointer();\n // A frame with the current where it was and no pull, now or on the last\n // frame, would draw exactly what is already on the canvas — the case of\n // `speed` 0 with the pointer outside the box.\n if (this.dirty || this.phase !== phase || pulled || this.pointer.pull > 0) this.draw(this.phase);\n this.rafId = requestAnimationFrame(tick);\n };\n this.rafId = requestAnimationFrame(tick);\n\n return () => {\n stopped = true;\n cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n stopPointer?.();\n };\n }\n\n /**\n * Track the pointer against the host's box. On `window`, not the host: the\n * content laid over the threads takes the events, and the host takes none.\n * `pointerleave` on the root element covers the rest — the pointer leaving the\n * page, and a finger lifting or a touch turning into a scroll, after which a\n * touch pointer no longer exists.\n */\n private listenToPointer(): () => void {\n const root = this.doc.documentElement;\n const onMove = (event: PointerEvent): void => {\n // Re-read the box on each move: cheaper than tracking scroll, and a\n // background rarely reflows mid-gesture. The rect is the box as DRAWN,\n // after any ancestor's transform, while the threads are drawn in the\n // layout box (see `setSize`), so the position is scaled from one to the\n // other — under a `scale()` the two differ, and the bend would land beside\n // the pointer.\n const rect = this.host.nativeElement.getBoundingClientRect();\n const { width, height } = this.bounds;\n const pointer = this.pointer;\n pointer.tx = (event.clientX - rect.left) * (rect.width > 0 ? width / rect.width : 1);\n pointer.ty = (event.clientY - rect.top) * (rect.height > 0 ? height / rect.height : 1);\n pointer.inside = pointer.tx >= 0 && pointer.ty >= 0 && pointer.tx <= width && pointer.ty <= height;\n // Arriving with no pull left, the bend starts where the pointer IS rather\n // than sweeping over from wherever it last let go.\n if (pointer.pull < 0.01) {\n pointer.x = pointer.tx;\n pointer.y = pointer.ty;\n }\n };\n const onLeave = (): void => {\n this.pointer.inside = false;\n };\n window.addEventListener('pointermove', onMove, { passive: true });\n root.addEventListener('pointerleave', onLeave);\n\n return () => {\n window.removeEventListener('pointermove', onMove);\n root.removeEventListener('pointerleave', onLeave);\n this.releasePointer();\n };\n }\n\n private easePointer(): void {\n const pointer = this.pointer;\n pointer.x += (pointer.tx - pointer.x) * POINTER_FOLLOW;\n pointer.y += (pointer.ty - pointer.y) * POINTER_FOLLOW;\n pointer.pull += ((pointer.inside ? 1 : 0) - pointer.pull) * POINTER_FADE;\n if (!pointer.inside && pointer.pull < 0.001) pointer.pull = 0;\n }\n\n private releasePointer(): void {\n this.pointer.inside = false;\n this.pointer.pull = 0;\n }\n\n /**\n * Size the bitmap and the drawing space from the LAYOUT box, never from\n * `getBoundingClientRect()`, which is the box after every ancestor's\n * transform. A `ResizeObserver` does not fire when only a transform changes,\n * so a boot inside an opening `wr-dialog` — which enters from `scale(0.96)` —\n * would otherwise keep a bitmap 4% too small, stretched soft, until the\n * window next resized.\n */\n private setSize(): void {\n const host = this.host.nativeElement;\n const width = host.clientWidth;\n const height = host.clientHeight;\n this.bounds = { width, height };\n const canvas = this.canvasRef().nativeElement;\n // Backed at the screen's pixel ratio, capped at 2 and by the pixel budget,\n // never below 1 — a bitmap the size of the CSS box smears a 1px thread\n // across two device pixels 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 canvas.width = Math.round(width * ratio);\n canvas.height = Math.round(height * ratio);\n // Resizing the bitmap resets the context, transform included, so the scale\n // goes on afterwards. Every coordinate below stays in CSS pixels.\n this.ctx?.setTransform(ratio, 0, 0, ratio, 0, 0);\n }\n\n // Drawing\n\n /**\n * The frame shown while nothing moves, with no pointer: under reduced motion\n * the current at its start, and at `speed` 0 with nothing to follow, the\n * current where it was held.\n */\n private drawStill(): void {\n this.releasePointer();\n this.draw(this.platform.prefersReducedMotion() ? 0 : this.phase);\n }\n\n /** Bring the canvas up to date after the picture changed: a still frame now, or the loop's next one. */\n private repaint(moving: boolean): void {\n if (moving) this.dirty = true;\n else this.drawStill();\n }\n\n /**\n * One frame of the field at `time` seconds of current.\n *\n * Each thread's height at `x` is the sum of three things: a meander the whole\n * bundle shares, the thread's place in the band — scaled by how wide the band\n * is breathing at that `x` — and a ripple of the thread's own, whose phase is\n * spaced by the golden angle and whose wavelength grows toward the edges, so\n * neighbours drift in and out of step. Wavelengths are in px rather than in\n * shares of the width, so a phone and a wide monitor show the same current.\n * The pointer then pulls each height toward its own, weighted by a bell in\n * both directions.\n */\n private draw(time: number): void {\n const ctx = this.ctx;\n if (!ctx) return;\n if (!this.painted()) this.painted.set(true);\n this.dirty = false;\n\n const { width, height } = this.bounds;\n ctx.clearRect(0, 0, width, height);\n\n const count = this.count();\n const amplitude = this.amplitude();\n const band = height * this.bandShare();\n const middle = height / 2;\n const samples = Math.max(2, Math.ceil(width / SAMPLE_STEP) + 1);\n const last = samples - 1;\n if (this.ys.length < samples) {\n this.ys = new Float64Array(samples);\n this.meanders = new Float64Array(samples);\n this.breaths = new Float64Array(samples);\n this.alongs = new Float64Array(samples);\n }\n const { ys, meanders, breaths, alongs } = this;\n\n const pointer = this.pointer;\n const pull = pointer.pull;\n const reachX = Math.max(100, width * 0.15);\n const reachY = Math.max(60, band * 0.75 + Math.abs(amplitude));\n const spreadX = 1 / (2 * reachX * reachX);\n const spreadY = 1 / (2 * reachY * reachY);\n\n // What depends only on `x` and the time, shared by every thread.\n for (let s = 0; s < samples; s++) {\n const x = s === last ? width : s * SAMPLE_STEP;\n meanders[s] =\n 0.55 * Math.sin(x * (TAU / 1100) - time * 0.7) + 0.25 * Math.sin(x * (TAU / 520) + time * 0.45 + 1.7);\n breaths[s] = 0.65 + 0.35 * Math.sin(x * (TAU / 760) + time * 0.5 + 0.6);\n if (pull > 0) {\n const dx = x - pointer.x;\n alongs[s] = Math.exp(-dx * dx * spreadX);\n }\n }\n\n ctx.strokeStyle = this.resolveColor();\n ctx.lineWidth = this.strokeWidth();\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n\n for (let line = 0; line < count; line++) {\n const place = count === 1 ? 0 : line / (count - 1) - 0.5;\n const outward = Math.abs(place) * 2;\n const seed = line * GOLDEN_ANGLE;\n const rippleK = TAU / (260 + outward * 200);\n\n for (let s = 0; s < samples; s++) {\n const x = s === last ? width : s * SAMPLE_STEP;\n const ripple = 0.22 * Math.sin(x * rippleK - time * 1.4 + seed);\n let y = middle + amplitude * (meanders[s] + ripple) + place * band * breaths[s];\n if (pull > 0) {\n const along = alongs[s];\n if (along > 0.002) {\n const dy = pointer.y - y;\n y += dy * POINTER_PULL * pull * along * Math.exp(-dy * dy * spreadY);\n }\n }\n ys[s] = y;\n }\n\n // The outer threads fade, which reads as depth: the middle of the bundle\n // in front, its edges behind.\n ctx.globalAlpha = 1 - EDGE_FADE * outward;\n ctx.beginPath();\n ctx.moveTo(0, ys[0]);\n // A curve through the MIDPOINTS, with each sample as its control, the way\n // `wr-waves` bends its lines: consecutive curves share a tangent where they\n // meet, so the thread has no corner anywhere.\n for (let s = 1; s < last; s++) {\n const x = s * SAMPLE_STEP;\n const next = s + 1 === last ? width : x + SAMPLE_STEP;\n ctx.quadraticCurveTo(x, ys[s], (x + next) / 2, (ys[s] + ys[s + 1]) / 2);\n }\n ctx.lineTo(width, ys[last]);\n ctx.stroke();\n }\n ctx.globalAlpha = 1;\n }\n\n /**\n * The input, or the theme's hook when it is emptied, resolved through the\n * cascade: `strokeStyle` reads neither a custom property nor a `light-dark()`.\n * Called on every draw, so a scheme switch lands on the next frame.\n */\n private resolveColor(): string {\n const expression = this.color().trim() || THEME_COLOR;\n return resolveCssColor(this.canvasRef().nativeElement, expression, FALLBACK_COLOR);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-bits-threads.mjs","names":["DOCUMENT"],"sources":["../../../projects/lib/bits/threads/threads.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 Threads background idea on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/threads), written for ngwr from\n * scratch: the original is a WebGL shader, this is a 2D canvas, and the motion,\n * the inputs and the names are this library's own.\n *\n * A 2D canvas rather than WebGL, deliberately. The picture is a few dozen thin\n * strokes, which a 2D context antialiases on its own; it boots with no shader to\n * compile, and it still draws on the machines that blocklist WebGL2.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { DOCUMENT, 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 { clamp, numAttr, resolveCssColor, watchColorScheme } from 'ngwr/utils';\n\n/** The theme's hook. A `color` input left at this, or emptied, follows the theme. */\nconst THEME_COLOR = 'var(--wr-threads-color)';\n\n/** What a stroke falls back to when no colour resolves: the hook's light value, flattened. */\nconst FALLBACK_COLOR = 'rgba(57, 105, 226, 0.6)';\n\n/** The most threads drawn. Past this the band is a fill, not strands, and the frame cost keeps climbing. */\nconst MAX_LINES = 100;\n\n/** The thinnest stroke drawn, in CSS px. A canvas ignores a zero or negative `lineWidth` and keeps the last one. */\nconst MIN_LINE_WIDTH = 0.25;\n\n/** Horizontal distance between the points a thread is curved through, in CSS px. */\nconst SAMPLE_STEP = 12;\n\n/**\n * The most bitmap pixels the canvas is backed with — the same budget `wr-waves`\n * keeps, for the same reason: the field is redrawn every frame, so the backing\n * store is a per-frame cost, and a wide retina window steps down toward 1x\n * instead of growing a 3456-pixel bitmap.\n */\nconst MAX_CANVAS_PIXELS = 2_500_000;\n\n/**\n * The longest step the current takes in one frame, in ms. A frame that arrives\n * late — a tab coming back, a long task — advances the threads by this much and\n * no more, so they never jump to a shape the reader did not see them reach.\n */\nconst MAX_FRAME_MS = 50;\n\n/** How much fainter the outermost threads are than the middle one, 0..1. */\nconst EDGE_FADE = 0.6;\n\n/** Share of the distance to the pointer a thread right under it bends. */\nconst POINTER_PULL = 0.55;\n\n/** Per-frame easing of the pointer's position and of its pull. */\nconst POINTER_FOLLOW = 0.12;\nconst POINTER_FADE = 0.06;\n\nconst TAU = Math.PI * 2;\n\n/** The golden angle, in radians: spaces each thread's ripple phase so no two neighbours move in step. */\nconst GOLDEN_ANGLE = 2.399963;\n\n/**\n * Threads background — thin lines flowing across the box like strands in a\n * current. The bundle meanders as one, breathes wider and narrower along its\n * length, and every strand ripples on its own phase, so neighbours cross and\n * part. With `followPointer` the strands near the pointer bend gently toward\n * it. Fills its nearest positioned ancestor: put it first in a\n * `position: relative` box and give the content after it a position of its own.\n *\n * Decorative only: the canvas is `aria-hidden`, the host has no role and\n * nothing in it takes focus or a click.\n *\n * Under `prefers-reduced-motion` one still frame is drawn and nothing follows —\n * no current, no pointer listeners, no frame loop. The frame is drawn again when\n * an input changes, the box resizes or the colour scheme switches, and the\n * preference is read live, so switching it while the page is open takes effect\n * at once.\n *\n * At `speed` 0 with `followPointer` off nothing can move either, so the same\n * still frame is drawn — held where the current was — and no loop runs. At\n * `speed` 0 with the pointer followed, the loop runs and draws only while the\n * pointer is bending something.\n *\n * Off screen, or with no box to draw on, the loop stops asking for frames and resumes\n * where it left off.\n *\n * The colour is theme-aware by default — `--wr-threads-color`, a soft wash of\n * the primary intent — and `[color]` takes any CSS colour instead, a `var()`\n * token or a `light-dark()` pair included. A canvas reads neither, so either is\n * resolved through the cascade before it reaches a stroke.\n *\n * Until the canvas has drawn — the seconds before hydration on a prerendered\n * page — the stylesheet paints the threads at rest as straight lines, at the\n * same count, colour, width and spread, and `.wr-threads--painted` retires them.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 20rem\">\n * <wr-threads />\n * <h1 style=\"position: relative\">Hero copy above the threads</h1>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/threads\n */\n@Component({\n selector: 'wr-threads',\n template: '<canvas #canvas class=\"wr-threads__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './threads.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-threads',\n '[class.wr-threads--painted]': 'painted()',\n // Feed the CSS stand-in (see styles/_rules.scss) so the straight lines it\n // draws before hydration sit where the canvas will put the threads.\n '[style.--wr-threads-input-color]': 'customColor()',\n '[style.--wr-threads-count]': 'count()',\n '[style.--wr-threads-spread]': 'bandShare()',\n '[style.--wr-threads-line-width.px]': 'strokeWidth()',\n },\n})\nexport class WrThreads {\n /** How many threads, rounded and kept between 1 and 100. @default 18 */\n readonly lineCount = input(18, { transform: numAttr(18) });\n\n /**\n * Thread colour, any CSS colour — a `var()` token or a `light-dark()` pair\n * included. Emptied, it follows the theme. @default 'var(--wr-threads-color)'\n */\n readonly color = input(THEME_COLOR);\n\n /** How far the bundle meanders up and down, in px. @default 40 */\n readonly amplitude = input(40, { transform: numAttr(40) });\n\n /** Speed of the current; 0 holds it, a negative value reverses it. @default 1 */\n readonly speed = input(1, { transform: numAttr(1) });\n\n /** Stroke width of each thread, in px. @default 1 */\n readonly lineWidth = input(1, { transform: numAttr(1) });\n\n /** Share of the box height the threads fan across at their widest, 0..1. @default 0.4 */\n readonly spread = input(0.4, { transform: numAttr(0.4) });\n\n /** Whether the threads near the pointer bend toward it. @default true */\n readonly followPointer = input(true, { transform: coerceBooleanProperty });\n\n /** The thread count actually drawn. */\n protected readonly count = computed(() => clamp(Math.round(this.lineCount()), 1, MAX_LINES));\n\n /** The stroke width actually drawn. */\n protected readonly strokeWidth = computed(() => Math.max(MIN_LINE_WIDTH, this.lineWidth()));\n\n /** The band's share of the height. One thread has no band: it runs through the middle. */\n protected readonly bandShare = computed(() => (this.count() > 1 ? clamp(this.spread(), 0, 1) : 0));\n\n /**\n * A colour the author chose, published so the stand-in draws in it too; `null`\n * while the input is the theme's. On a variable of its own rather than on the\n * hook, which an author sets from outside: a `null` binding wipes a value of its\n * own name from the host's `style` attribute.\n */\n protected readonly customColor = computed(() => {\n const color = this.color().trim();\n return color === '' || color === THEME_COLOR ? null : color;\n });\n\n /**\n * Whether anything can change between two frames: never under\n * `prefers-reduced-motion`, read live, and not at `speed` 0 with no pointer to\n * follow. While it is `false` there is no loop, only a still frame.\n */\n private readonly moving = computed(\n () => !this.platform.prefersReducedMotion() && (this.speed() !== 0 || this.followPointer())\n );\n\n /**\n * Flips once the canvas has drawn, which retires the CSS stand-in. Set inside\n * the draw rather than at the end of the boot, so it can never switch the\n * stand-in off over a canvas with nothing on it.\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 doc = inject(DOCUMENT);\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 private bounds = { width: 0, height: 0 };\n private rafId = 0;\n\n /** Seconds of current so far, already scaled by `speed`. Kept across a parked loop, so it resumes in place. */\n private phase = 0;\n\n /** Eased pointer position in host px (`x`, `y`), where it is heading (`tx`, `ty`), and how hard it pulls, 0..1. */\n private readonly pointer = { x: 0, y: 0, tx: 0, ty: 0, inside: false, pull: 0 };\n\n /**\n * Per-sample buffers, reused frame to frame rather than allocated per thread:\n * one thread's heights, and the three terms every thread shares at a sample —\n * the bundle's meander, how wide the band is breathing, and the pointer's bell\n * along `x` — computed once per frame instead of once per thread.\n */\n private ys = new Float64Array(0);\n private meanders = new Float64Array(0);\n private breaths = new Float64Array(0);\n private alongs = new Float64Array(0);\n\n /**\n * Whether the picture changed since the loop last drew it — an input, the\n * colour scheme, a restart. The loop skips the frames that would only repeat\n * the last one (a held current, no pull), and this is what makes it draw again.\n */\n private dirty = true;\n\n /** Flips once `boot()` has a context — everything that draws waits for it. */\n private readonly booted = signal(false);\n\n /** Whether the host is in the viewport; stays `true` without an observer. */\n private readonly onScreen = signal(true);\n\n /**\n * Whether the box has an area. The intersection observer calls a zero-area host\n * intersecting and sends no entry when it gains a size, so this reads the box instead.\n */\n private readonly hasBox = signal(false);\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => this.zone.runOutsideAngular(() => this.boot()));\n\n // A still frame stays as it was drawn, so with nothing moving every input\n // that changes the picture paints it again — and so does the moment motion\n // stops. A running loop is only told the picture changed: it skips the\n // frames that would repeat the last one, and draws the next one again. After\n // render rather than an `effect`, which runs before a directive's host binding:\n // a still frame drawn then would miss a hook it changes in the same pass.\n afterRenderEffect(() => {\n if (!this.booted()) return;\n const moving = this.moving();\n this.count();\n this.bandShare();\n this.amplitude();\n this.strokeWidth();\n this.color();\n untracked(() => this.repaint(moving));\n });\n\n // The same for the colour scheme: a stroke keeps the colour it was resolved\n // in, and nothing else redraws a still frame or a held loop.\n watchColorScheme(() => {\n if (this.booted()) this.repaint(this.moving());\n });\n\n // The current runs only while there is something to see: never while\n // nothing can move (see `moving`), and not while the host is off screen or has\n // no box. Toggling `followPointer` restarts it with or without the pointer\n // listeners.\n effect(onCleanup => {\n if (!this.booted() || !this.onScreen() || !this.hasBox() || !this.moving()) return;\n const follow = this.followPointer();\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.animate(follow))));\n });\n }\n\n // Setup\n\n private boot(): void {\n const host = this.host.nativeElement;\n this.ctx = this.canvasRef().nativeElement.getContext('2d');\n if (!this.ctx) return;\n\n this.setSize();\n\n // Resizing the bitmap clears it, so the frame is drawn again right away\n // rather than left blank until the next one: a ResizeObserver callback runs\n // after that frame's animation callbacks and before its paint.\n const onResize = (): void => {\n this.setSize();\n if (this.moving()) this.draw(this.phase);\n else this.drawStill();\n };\n window.addEventListener('resize', onResize);\n const ro = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(onResize);\n ro?.observe(host);\n\n // Off screen the loop stops asking for frames: a background redrawn out of\n // sight still costs the browser a frame every frame. Without an observer\n // (jsdom) the host simply counts as visible. A box flush against a zero-height\n // clip intersects with nothing showing, and only a second threshold reports the\n // clip moving. It sits below any real ratio, or a clip closing on a transition\n // steps from under it to 0 without crossing anything.\n const io =\n typeof IntersectionObserver === 'undefined'\n ? null\n : new IntersectionObserver(\n entries => {\n const { isIntersecting, intersectionRatio } = entries[entries.length - 1];\n this.onScreen.set(isIntersecting && intersectionRatio > 0);\n },\n { threshold: [0, 1e-9] }\n );\n io?.observe(host);\n\n this.destroyRef.onDestroy(() => {\n window.removeEventListener('resize', onResize);\n ro?.disconnect();\n io?.disconnect();\n });\n this.booted.set(true);\n }\n\n /** Start the current, and the pointer listeners when asked; returns what stops both. */\n private animate(follow: boolean): () => void {\n const stopPointer = follow ? this.listenToPointer() : null;\n let last = -1;\n // A frame already handed to the browser can still arrive after the stop —\n // a held or polyfilled scheduler does not always honour the cancel — and a\n // late one must not draw, nor schedule a loop nobody will stop again.\n let stopped = false;\n // The canvas holds whatever was drawn before the loop stopped, which may not\n // be the picture now: the first frame always draws.\n this.dirty = true;\n const tick = (now: number): void => {\n if (stopped) return;\n const phase = this.phase;\n if (last >= 0) this.phase += (clamp(now - last, 0, MAX_FRAME_MS) / 1000) * this.speed();\n last = now;\n const pulled = this.pointer.pull > 0;\n this.easePointer();\n // A frame with the current where it was and no pull, now or on the last\n // frame, would draw exactly what is already on the canvas — the case of\n // `speed` 0 with the pointer outside the box.\n if (this.dirty || this.phase !== phase || pulled || this.pointer.pull > 0) this.draw(this.phase);\n this.rafId = requestAnimationFrame(tick);\n };\n this.rafId = requestAnimationFrame(tick);\n\n return () => {\n stopped = true;\n cancelAnimationFrame(this.rafId);\n this.rafId = 0;\n stopPointer?.();\n };\n }\n\n /**\n * Track the pointer against the host's box. On `window`, not the host: the\n * content laid over the threads takes the events, and the host takes none.\n * `pointerleave` on the root element covers the rest — the pointer leaving the\n * page, and a finger lifting or a touch turning into a scroll, after which a\n * touch pointer no longer exists.\n */\n private listenToPointer(): () => void {\n const root = this.doc.documentElement;\n const onMove = (event: PointerEvent): void => {\n // Re-read the box on each move: cheaper than tracking scroll, and a\n // background rarely reflows mid-gesture. The rect is the box as DRAWN,\n // after any ancestor's transform, while the threads are drawn in the\n // layout box (see `setSize`), so the position is scaled from one to the\n // other — under a `scale()` the two differ, and the bend would land beside\n // the pointer.\n const rect = this.host.nativeElement.getBoundingClientRect();\n const { width, height } = this.bounds;\n const pointer = this.pointer;\n pointer.tx = (event.clientX - rect.left) * (rect.width > 0 ? width / rect.width : 1);\n pointer.ty = (event.clientY - rect.top) * (rect.height > 0 ? height / rect.height : 1);\n pointer.inside = pointer.tx >= 0 && pointer.ty >= 0 && pointer.tx <= width && pointer.ty <= height;\n // Arriving with no pull left, the bend starts where the pointer IS rather\n // than sweeping over from wherever it last let go.\n if (pointer.pull < 0.01) {\n pointer.x = pointer.tx;\n pointer.y = pointer.ty;\n }\n };\n const onLeave = (): void => {\n this.pointer.inside = false;\n };\n window.addEventListener('pointermove', onMove, { passive: true });\n root.addEventListener('pointerleave', onLeave);\n\n return () => {\n window.removeEventListener('pointermove', onMove);\n root.removeEventListener('pointerleave', onLeave);\n this.releasePointer();\n };\n }\n\n private easePointer(): void {\n const pointer = this.pointer;\n pointer.x += (pointer.tx - pointer.x) * POINTER_FOLLOW;\n pointer.y += (pointer.ty - pointer.y) * POINTER_FOLLOW;\n pointer.pull += ((pointer.inside ? 1 : 0) - pointer.pull) * POINTER_FADE;\n if (!pointer.inside && pointer.pull < 0.001) pointer.pull = 0;\n }\n\n private releasePointer(): void {\n this.pointer.inside = false;\n this.pointer.pull = 0;\n }\n\n /**\n * Size the bitmap and the drawing space from the LAYOUT box, never from\n * `getBoundingClientRect()`, which is the box after every ancestor's\n * transform. A `ResizeObserver` does not fire when only a transform changes,\n * so a boot inside an opening `wr-dialog` — which enters from `scale(0.96)` —\n * would otherwise keep a bitmap 4% too small, stretched soft, until the\n * window next resized.\n */\n private setSize(): void {\n const host = this.host.nativeElement;\n const width = host.clientWidth;\n const height = host.clientHeight;\n this.bounds = { width, height };\n this.hasBox.set(width > 0 && height > 0);\n const canvas = this.canvasRef().nativeElement;\n // Backed at the screen's pixel ratio, capped at 2 and by the pixel budget,\n // never below 1 — a bitmap the size of the CSS box smears a 1px thread\n // across two device pixels 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 canvas.width = Math.round(width * ratio);\n canvas.height = Math.round(height * ratio);\n // Resizing the bitmap resets the context, transform included, so the scale\n // goes on afterwards. Every coordinate below stays in CSS pixels.\n this.ctx?.setTransform(ratio, 0, 0, ratio, 0, 0);\n }\n\n // Drawing\n\n /**\n * The frame shown while nothing moves, with no pointer: under reduced motion\n * the current at its start, and at `speed` 0 with nothing to follow, the\n * current where it was held.\n */\n private drawStill(): void {\n this.releasePointer();\n this.draw(this.platform.prefersReducedMotion() ? 0 : this.phase);\n }\n\n /** Bring the canvas up to date after the picture changed: a still frame now, or the loop's next one. */\n private repaint(moving: boolean): void {\n if (moving) this.dirty = true;\n else this.drawStill();\n }\n\n /**\n * One frame of the field at `time` seconds of current.\n *\n * Each thread's height at `x` is the sum of three things: a meander the whole\n * bundle shares, the thread's place in the band — scaled by how wide the band\n * is breathing at that `x` — and a ripple of the thread's own, whose phase is\n * spaced by the golden angle and whose wavelength grows toward the edges, so\n * neighbours drift in and out of step. Wavelengths are in px rather than in\n * shares of the width, so a phone and a wide monitor show the same current.\n * The pointer then pulls each height toward its own, weighted by a bell in\n * both directions.\n */\n private draw(time: number): void {\n const ctx = this.ctx;\n if (!ctx) return;\n if (!this.painted()) this.painted.set(true);\n this.dirty = false;\n\n const { width, height } = this.bounds;\n ctx.clearRect(0, 0, width, height);\n\n const count = this.count();\n const amplitude = this.amplitude();\n const band = height * this.bandShare();\n const middle = height / 2;\n const samples = Math.max(2, Math.ceil(width / SAMPLE_STEP) + 1);\n const last = samples - 1;\n if (this.ys.length < samples) {\n this.ys = new Float64Array(samples);\n this.meanders = new Float64Array(samples);\n this.breaths = new Float64Array(samples);\n this.alongs = new Float64Array(samples);\n }\n const { ys, meanders, breaths, alongs } = this;\n\n const pointer = this.pointer;\n const pull = pointer.pull;\n const reachX = Math.max(100, width * 0.15);\n const reachY = Math.max(60, band * 0.75 + Math.abs(amplitude));\n const spreadX = 1 / (2 * reachX * reachX);\n const spreadY = 1 / (2 * reachY * reachY);\n\n // What depends only on `x` and the time, shared by every thread.\n for (let s = 0; s < samples; s++) {\n const x = s === last ? width : s * SAMPLE_STEP;\n meanders[s] =\n 0.55 * Math.sin(x * (TAU / 1100) - time * 0.7) + 0.25 * Math.sin(x * (TAU / 520) + time * 0.45 + 1.7);\n breaths[s] = 0.65 + 0.35 * Math.sin(x * (TAU / 760) + time * 0.5 + 0.6);\n if (pull > 0) {\n const dx = x - pointer.x;\n alongs[s] = Math.exp(-dx * dx * spreadX);\n }\n }\n\n ctx.strokeStyle = this.resolveColor();\n ctx.lineWidth = this.strokeWidth();\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n\n for (let line = 0; line < count; line++) {\n const place = count === 1 ? 0 : line / (count - 1) - 0.5;\n const outward = Math.abs(place) * 2;\n const seed = line * GOLDEN_ANGLE;\n const rippleK = TAU / (260 + outward * 200);\n\n for (let s = 0; s < samples; s++) {\n const x = s === last ? width : s * SAMPLE_STEP;\n const ripple = 0.22 * Math.sin(x * rippleK - time * 1.4 + seed);\n let y = middle + amplitude * (meanders[s] + ripple) + place * band * breaths[s];\n if (pull > 0) {\n const along = alongs[s];\n if (along > 0.002) {\n const dy = pointer.y - y;\n y += dy * POINTER_PULL * pull * along * Math.exp(-dy * dy * spreadY);\n }\n }\n ys[s] = y;\n }\n\n // The outer threads fade, which reads as depth: the middle of the bundle\n // in front, its edges behind.\n ctx.globalAlpha = 1 - EDGE_FADE * outward;\n ctx.beginPath();\n ctx.moveTo(0, ys[0]);\n // A curve through the MIDPOINTS, with each sample as its control, the way\n // `wr-waves` bends its lines: consecutive curves share a tangent where they\n // meet, so the thread has no corner anywhere.\n for (let s = 1; s < last; s++) {\n const x = s * SAMPLE_STEP;\n const next = s + 1 === last ? width : x + SAMPLE_STEP;\n ctx.quadraticCurveTo(x, ys[s], (x + next) / 2, (ys[s] + ys[s + 1]) / 2);\n }\n ctx.lineTo(width, ys[last]);\n ctx.stroke();\n }\n ctx.globalAlpha = 1;\n }\n\n /**\n * The input, or the theme's hook when it is emptied, resolved through the\n * cascade: `strokeStyle` reads neither a custom property nor a `light-dark()`.\n * Called on every draw, so a scheme switch lands on the next frame.\n */\n private resolveColor(): string {\n const expression = this.color().trim() || THEME_COLOR;\n return resolveCssColor(this.canvasRef().nativeElement, expression, FALLBACK_COLOR);\n }\n}\n"],"mappings":"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"}
|