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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngwr-bits-iridescence.mjs","names":[],"sources":["../../../projects/lib/bits/iridescence/iridescence.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 Iridescence background idea on reactbits.dev\n * (https://www.reactbits.dev/backgrounds/iridescence). The shader is written for\n * ngwr from scratch: folded silk under a raking light, with the motion, the\n * inputs and the names this library's own.\n *\n * WebGL2 rather than a 2D canvas, deliberately, unlike `wr-threads` and\n * `wr-ripple-grid`. Those draw a few dozen strokes; this works out every pixel,\n * every frame, which is a job for a fragment shader and no job for a 2D context.\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n NgZone,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n afterRenderEffect,\n computed,\n effect,\n inject,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\nimport { clamp, numAttr, resolveCssColor, resolveCssColorChannels, watchColorScheme } from 'ngwr/utils';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\nuniform vec2 uResolution;\nuniform vec3 uPhase;\nuniform vec2 uPointer;\nuniform vec3 uColor;\nout vec4 fragColor;\n\n// Folded silk. A height field of draped folds is lit by a raking key light; hue is a small, luma-free offset\n// driven by the fold slope. No finite differences: every stage carries its gradient by the chain rule, so the\n// normal costs one evaluation of the cloth instead of three.\n\n// One stage of the warp: moves v by a sine of u, and carries the gradient of v along with it.\nvoid shear(inout float v, inout vec2 gv, float u, vec2 gu, float amp, float k, float phi) {\n v += amp * sin(k * u + phi);\n gv += amp * k * cos(k * u + phi) * gu;\n}\n\n// One family of folds added to the height. Negative skew bends the sine so the flank facing the light is short\n// and bright and the other long and soft, which is the drape's crest and valley. t tapers the family, gt is its\n// gradient.\nvoid pleat(inout float h, inout vec2 g, float u, vec2 gu, float skew, float amp, float t, vec2 gt) {\n float b = u + skew * sin(u);\n float s = sin(b);\n h += amp * t * s;\n g += amp * (t * cos(b) * (1.0 + skew * cos(u)) * gu + s * gt);\n}\n\nvoid main() {\n vec2 p = (2.0 * gl_FragCoord.xy - uResolution) / min(uResolution.x, uResolution.y);\n vec3 ph = uPhase;\n vec2 st = uPointer;\n\n // Drape axis: q.x runs along the folds, q.y across them. gx and gy are the gradients of q.x and q.y.\n vec2 gx = vec2(0.86, 0.51);\n vec2 gy = vec2(-0.51, 0.86);\n vec2 q = vec2(dot(p, gx), dot(p, gy)) + st * vec2(0.5, 0.8);\n\n // The warp. The pointer enters each stage differently, so stirring bends the folds instead of sliding the\n // picture. Sums like ph.y - ph.x are still integer multiples, so seam free, and give slow beats: the large\n // displacements run on those so the cloth does not stream.\n\n // A long sweep: the whole drape bends once across the frame.\n shear(q.y, gy, q.x, gx, 0.42, 0.60, ph.x + 1.6 * st.x);\n\n // Gather: spacing across the drape swells and tightens along its length, so folds fan out from a pinch.\n float ga = 0.85 * q.x + ph.y - ph.x + 1.2 * st.y;\n float gs = 0.20 * sin(ga);\n gy = (1.0 + gs) * gy + q.y * 0.20 * 0.85 * cos(ga) * gx;\n q.y *= 1.0 + gs;\n\n // A slip along the folds, so tapers and crossings do not line up across the width.\n shear(q.x, gx, q.y, gy, 0.22, 1.10, -ph.y - 0.8 * st.y);\n\n // Differential bend: this one reads q.y too, so neighbouring folds bend apart and bunch instead of running\n // as parallel stripes.\n shear(q.y, gy, q.x + 0.8 * q.y, gx + 0.8 * gy, 0.24, 1.15, ph.z - ph.y - 2.0 * st.x);\n\n // Height, three scales of cloth: a big billow slightly off axis that decides where the light pools, the main\n // folds, and a fine echo. Each main fold is gated across the drape as well as along it, so some stand proud\n // and others die out; that hierarchy is what stops it reading as stripes. The tapers run on the fast phase:\n // folds swell and relax in place more than they travel.\n float h = 0.0;\n vec2 g = vec2(0.0);\n pleat(h, g, 2.3 * q.y - 0.8 * q.x - ph.x + 2.5 * st.y - 1.5 * st.x, 2.3 * gy - 0.8 * gx, -0.2, 0.62, 1.0, vec2(0.0));\n float a = 0.9 * q.x + 1.3 * q.y - ph.z;\n pleat(h, g, 5.0 * q.y + ph.x, 5.0 * gy, -0.36, 0.50, 0.68 + 0.32 * sin(a), 0.32 * cos(a) * (0.9 * gx + 1.3 * gy));\n a = 1.1 * q.y + 0.6 * q.x + 1.9 + ph.z;\n pleat(h, g, 9.5 * q.y + 0.5 * q.x - ph.y, 9.5 * gy + 0.5 * gx, -0.34, 0.20, 0.55 + 0.45 * sin(a),\n 0.45 * cos(a) * (1.1 * gy + 0.6 * gx));\n\n // Raking key light: a flat patch sits mid-tone, the two flanks of a crest go opposite ways. The sheen is a broad\n // lobe that rides the lit flank. A weak bounce from the far side keeps the shaded flank from going to slate.\n const vec3 L = normalize(vec3(-0.50, 0.72, 0.48));\n const vec3 H = normalize(L + vec3(0.0, 0.0, 1.0));\n const vec3 B = normalize(vec3(0.42, -0.60, 0.68));\n vec3 n = normalize(vec3(-0.27 * g, 1.0));\n float body = pow(0.5 + 0.5 * dot(n, L), 1.35);\n float sheen = pow(max(dot(n, H), 0.0), 26.0);\n float bounce = pow(max(dot(n, B), 0.0), 8.0);\n\n // Valleys sit in their own shade whatever the light does; this keeps depth in the dark tint.\n float lum = (0.21 + 1.12 * body) * (0.90 + 0.15 * h) + 0.30 * sheen + 0.08 * bounce;\n\n // Shot silk: the hue turns with the slope seen from the light, pale rose on the lit flank, lilac on the flat,\n // periwinkle in the shade. The drift along the warped cloth wanders further where it is lit, so pale sky and rose\n // zones follow the folds while the shade holds its colour: a warm highlight or a green-heavy shade would grey\n // or flatten under the dark tint.\n float slope = dot(g, vec2(-0.57, 0.82));\n float turn = 0.42 * slope / sqrt(1.0 + 0.18 * slope * slope);\n float drift = sin(1.25 * q.y + 0.55 * q.x + ph.z - ph.y);\n float theta = 1.68 + 0.85 * turn - 0.2 * turn * turn + (0.15 + 0.85 * body) * drift;\n vec3 tone = cos(theta + vec3(0.0, 2.1, 4.2));\n\n // The offset has its luma removed, so colour draws no shapes of its own and the folds stay pure luminance.\n tone -= dot(tone, vec3(0.2126, 0.7152, 0.0722));\n\n // Chroma lives in the shade and the mid-tones; the highlight goes nearly white, which is what makes it pearl.\n vec3 col = lum * (1.0 + (0.46 - 0.28 * body - 0.08 * sheen) * tone);\n\n // Soft shoulder, so a highlight rolls off instead of clipping to a flat white shape.\n col = col / (1.0 + 0.26 * col * col);\n\n // Interleaved gradient noise, plus or minus half an 8-bit step: a large dark hero spreads a few levels of tint over\n // hundreds of pixels and bands. Static, off the pixel and never a phase, so it cannot shimmer or cost a held frame a redraw.\n float dither = fract(52.9829189 * fract(dot(gl_FragCoord.xy, vec2(0.06711056, 0.00583715)))) - 0.5;\n\n fragColor = vec4(col * uColor + dither / 255.0, 1.0);\n}\n`;\n\n/** What the GPU holds for one context. A lost context takes all of it, so a restored one builds it again. */\ninterface Gpu {\n readonly program: WebGLProgram;\n readonly buffer: WebGLBuffer | null;\n readonly resolution: WebGLUniformLocation | null;\n readonly phase: WebGLUniformLocation | null;\n readonly pointer: WebGLUniformLocation | null;\n readonly color: WebGLUniformLocation | null;\n}\n\n/** The theme's hook. A `color` input left unset, emptied or blank follows it. */\nconst THEME_COLOR = 'var(--wr-iridescence-color)';\n\n/** What the tint falls back to when no colour resolves: the hook's light value, which leaves the picture as it is. */\nconst FALLBACK_COLOR = 'rgb(255, 255, 255)';\n\nconst TAU = Math.PI * 2;\n\n/**\n * How fast each phase turns at `speed` 1, in radians per second. The three have no\n * common factor, so at `speed` 1 the picture comes back round only after 2000π\n * seconds, an hour and three quarters.\n */\nconst RATE = [0.187, 0.283, 0.441] as const;\n\n/** A 60 Hz frame, in ms: what the first frame of a run advances by, with no frame before it to measure from. */\nconst FRAME_MS = 1000 / 60;\n\n/** The longest step one frame may take, in ms, so a tab that wakes after a pause does not jump the folds. */\nconst MAX_FRAME_MS = 50;\n\n/**\n * How far the pointer stirs the folds at `amplitude` 1, with the pointer on an\n * edge of the box, in the shader's units (the short side of the box is 2).\n */\nconst REACH = 0.1;\n\n/** The pointer's easing time constant, in ms: it covers 63% of the way to its target in this long, at any frame rate. */\nconst POINTER_TAU_MS = 250;\n\n/**\n * How close the eased pointer has to be to its target to land on it. A step\n * of an exponential never arrives on its own, and a pointer still arriving would\n * keep a held picture redrawing.\n */\nconst POINTER_EPSILON = 1e-4;\n\n/**\n * The most bitmap pixels the canvas is backed with. The picture is soft, so it\n * is backed at CSS pixels rather than device pixels to begin with, and a box too\n * large even for that scales both sides down: the fragment shader runs once per\n * bitmap pixel, every frame. `uResolution` follows the bitmap and the shader\n * normalises by its short side, so the picture does not change with it.\n */\nconst MAX_PIXELS = 2_000_000;\n\n/**\n * `angle` brought into `[0, 2π)`, for a negative one as well. The shader reads a\n * phase only through sin and cos of a whole multiple of it, so the wrap is\n * invisible, and a phase that never grows never loses the precision a clock\n * would lose in a tab left open for a week.\n *\n * The last line is for a tiny negative angle, which rounds up to exactly 2π when\n * it is added back, and for an infinite `speed`, whose remainder is `NaN`: a `NaN`\n * phase would hold every later one.\n */\nfunction wrap(angle: number): number {\n const turn = angle % TAU;\n const wrapped = turn < 0 ? turn + TAU : turn;\n return wrapped < TAU ? wrapped : 0;\n}\n\n/**\n * Iridescence background — folded silk under a raking light, its hue turning\n * with the slope of each fold from pale rose through lilac to sky, on a WebGL2\n * canvas. The folds drift and swell on their own, and the pointer stirs them,\n * bending the drape rather than sliding the picture. Fills its nearest positioned\n * ancestor: put it first in a `position: relative` box and give the content after\n * it a position of its own.\n *\n * The pointer is followed on the host's PARENT, with passive listeners, so the\n * silk answers a pointer that is over the content in front of it rather than over\n * the canvas — the host itself takes no pointer events and never steals a click.\n * Placed directly in a shadow root, where it has no parent element, it follows\n * the pointer on the shadow host; with neither it listens nowhere.\n *\n * Decorative only: the canvas is `aria-hidden`, the host has no role and nothing\n * in it takes focus or a click.\n *\n * The frame loop runs only while the host is on screen with a box to draw on and\n * something can move: not at `speed` 0 with no pointer to follow, where one still\n * frame is drawn. At\n * `speed` 0 with the pointer followed, the loop runs and draws only while the\n * pointer is stirring something. Off screen it stops asking for frames and resumes\n * where it left off.\n *\n * Under `prefers-reduced-motion`, read live, one still frame is drawn and nothing\n * follows — no loop, no pointer listeners, the pointer back at rest. It is held at\n * the phases the folds had reached, so switching the preference on does not jump\n * them, and it is drawn again when the colour, the box or the page's colour scheme\n * changes.\n *\n * `[color]` is multiplied over the picture, so white leaves it as it is, and it\n * defaults to the `--wr-iridescence-color` hook: white on a light page, a deep\n * slate blue on a dark one. Any CSS colour works, a `var()` token or a\n * `light-dark()` pair included, resolved through the cascade before it reaches the\n * shader, which reads neither. Pass an opaque colour: the canvas drops alpha and\n * the stand-in keeps it.\n *\n * Measured over 240 phase combinations, with the pointer at rest and at the box's\n * four corners at `amplitude` 1, `--wr-color-on-surface` holds 4.5:1 on the dark\n * default (5.01:1 at the 99th-percentile pixel, 4.89:1 in the worst frame) and not\n * on the light one (3.93:1 at the 1st-percentile pixel, 2.8:1 in the worst frame),\n * so text there needs a scrim or a card.\n *\n * The host's own background is a stand-in: soft diagonal bands in the same palette,\n * which an opaque colour tints the same way. The stand-in shows before hydration on a\n * prerendered page, on a machine with no WebGL2 and after a lost context, so the box\n * is never empty and text placed on it keeps its ground. The canvas fades in over it,\n * because the stand-in is an approximation and not the same picture, so\n * `.wr-iridescence--painted` hands over rather than swaps.\n *\n * Under forced colours the canvas is hidden and its loop stops with it, leaving the\n * box on the palette's own ground, which is what forced text and marks are chosen\n * against.\n *\n * With no WebGL2 context, or a shader the driver refuses, nothing is thrown and\n * the stand-in stays. A context the browser takes away is rebuilt if it offers one\n * back. A destroyed host hands its GPU resources back at once. Browsers keep a small\n * number of live contexts and kill the oldest: Chromium frees the slot at that point,\n * while WebKit frees it only when the context is collected and offers an evicted one\n * no restore, so there a host that is not drawing can stay on the stand-in for good.\n *\n * @example\n * ```html\n * <section style=\"position: relative; min-height: 24rem; padding: 2rem; overflow: hidden\">\n * <wr-iridescence />\n * <wr-card style=\"max-width: 26rem\">\n * <h1>Hero copy on a card above the silk</h1>\n * </wr-card>\n * </section>\n * ```\n *\n * @see https://ngwr.dev/bits/iridescence\n */\n@Component({\n selector: 'wr-iridescence',\n template: '<canvas #canvas class=\"wr-iridescence__canvas\" aria-hidden=\"true\"></canvas>',\n styleUrl: './iridescence.scss',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-iridescence',\n // Feed the CSS stand-in (see `styles/_rules.scss`) so it takes the same tint,\n // and fade the canvas in over it once it has painted.\n '[style.--wr-iridescence-input-color]': 'standInColor()',\n '[class.wr-iridescence--painted]': 'painted()',\n },\n})\nexport class WrIridescence {\n /**\n * Tint multiplied over the picture, any CSS colour — a `var()` token or a\n * `light-dark()` pair included. Pass an opaque colour: the canvas drops alpha and\n * the stand-in keeps it. When unset, empty or blank, `--wr-iridescence-color`\n * decides.\n */\n readonly color = input<string | null>(null);\n\n /**\n * Speed of the folds; `0` holds them and a negative value reverses them.\n * Changing it never jumps the picture. @default 1\n */\n readonly speed = input(1, { transform: numAttr(1) });\n\n /**\n * How hard the pointer stirs the folds; `0` leaves the pointer out and a\n * negative value stirs them the other way. @default 1\n */\n readonly amplitude = input(1, { transform: numAttr(1) });\n\n /** Whether the pointer stirs the folds at all. @default true */\n readonly followPointer = input(true, { transform: coerceBooleanProperty });\n\n /**\n * A colour the author chose, published so the stand-in takes it too; `null` while\n * the input is unset. On a variable of its own rather than on the hook, which an\n * author sets from outside: a `null` binding wipes a value of its own name from the\n * host's `style` attribute.\n */\n protected readonly standInColor = computed(() => (this.color() ?? '').trim() || null);\n\n /** The colour the canvas resolves: an input that is unset, empty or blank leaves the hook in charge. */\n private readonly expression = computed(() => (this.color() ?? '').trim() || THEME_COLOR);\n\n /** Whether the pointer is listened for: never under `prefers-reduced-motion`, read live. */\n private readonly listening = computed(\n () => !this.platform.prefersReducedMotion() && this.followPointer() && this.amplitude() !== 0\n );\n\n /**\n * Whether anything can change between two frames. While it is `false` there is\n * no loop, only a still frame.\n */\n private readonly moving = computed(\n () => !this.platform.prefersReducedMotion() && (this.speed() !== 0 || this.listening())\n );\n\n /**\n * Flips once the canvas has drawn, which fades it in over the stand-in. Set\n * inside the draw rather than at the end of the boot, so a canvas with nothing\n * on it is never shown, and back to `false` with a lost context.\n */\n protected readonly painted = signal(false);\n\n private readonly canvasRef = viewChild.required<ElementRef<HTMLCanvasElement>>('canvas');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly zone = inject(NgZone);\n private readonly destroyRef = inject(DestroyRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private gl: WebGL2RenderingContext | null = null;\n private gpu: Gpu | null = null;\n /** The size the canvas is backed at, in bitmap pixels. */\n private bitmap = { width: 0, height: 0 };\n private rafId = 0;\n\n /** The three phases, each in `[0, 2π)`. Kept across a parked loop, so the folds resume in place. */\n private readonly phase = [0, 0, 0];\n\n /**\n * `uPointer` as drawn (`x`, `y`), and where it is heading before `amplitude`\n * (`aimX`, `aimY`). `amplitude` is applied per frame rather than kept in the aim,\n * so changing it eases over like any other move instead of jumping.\n */\n private readonly pointer = { x: 0, y: 0, aimX: 0, aimY: 0 };\n\n /** The last pointer position in viewport coordinates, while a frame still owes it a look at the host's rect. */\n private lastMove: { x: number; y: number } | null = null;\n\n /** The last colour string the cascade resolved to, and its channels: the parse is skipped while it holds. */\n private resolved = '';\n private channels: readonly [number, number, number] = [1, 1, 1];\n\n /**\n * Whether the picture changed since the loop last drew it — the colour, the\n * scheme, a restart. The loop skips the frames that would only repeat the last\n * one, and this is what makes it draw again.\n */\n private dirty = true;\n\n /** Flips once a program is built, and back when the context goes — 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 bitmap has an area. The intersection observer calls a zero-area\n * canvas intersecting and sends no entry when it gains a size, so this reads the\n * bitmap 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 a new colour\n // paints it again — and so does the moment motion stops. A running loop is\n // only told the picture changed. `speed`, `amplitude` and `followPointer` are\n // left out: none of them changes a picture at rest. After render rather than an\n // `effect`, which runs before a directive's host binding: a still frame drawn then\n // would miss a hook it changes in the same pass, and nothing draws it again.\n afterRenderEffect(() => {\n if (!this.booted()) return;\n const moving = this.moving();\n this.expression();\n untracked(() => this.repaint(moving));\n });\n\n // The same for the colour scheme: a frame keeps the colour it was resolved in,\n // 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 loop runs only while there is something to see: never while nothing can\n // move (see `moving`), and not while the host is off screen or has no box.\n // Toggling the pointer restarts it with or without the listeners.\n effect(onCleanup => {\n if (!this.booted() || !this.onScreen() || !this.hasBox() || !this.moving()) return;\n const follow = this.listening();\n onCleanup(untracked(() => this.zone.runOutsideAngular(() => this.animate(follow))));\n });\n }\n\n // Setup\n\n private boot(): void {\n const canvas = this.canvasRef().nativeElement;\n // Alpha stays on deliberately. The shader writes alpha 1, and in every state\n // with nothing drawn the canvas is transparent, so the stand-in shows through.\n const gl = canvas.getContext('webgl2', { alpha: true, antialias: false, depth: false, stencil: false });\n if (!gl) return;\n this.gl = gl;\n\n // A context can arrive already lost, past the browser's limit on live ones.\n // That is not a broken shader: the restore below builds it. A shader the\n // driver really refused has logged, and nothing will ever draw on this one.\n this.gpu = this.build(gl);\n if (!this.gpu && !gl.isContextLost()) {\n this.release(gl);\n return;\n }\n\n const onLost = (event: Event): void => {\n // Without this the browser never offers the context back.\n event.preventDefault();\n this.gpu = null;\n this.booted.set(false);\n this.painted.set(false);\n };\n const onRestored = (): void => {\n this.gpu = this.build(gl);\n if (this.gpu) this.booted.set(true);\n };\n canvas.addEventListener('webglcontextlost', onLost);\n canvas.addEventListener('webglcontextrestored', onRestored);\n\n // `clientWidth` and not `getBoundingClientRect()`: the rect is the box after\n // transforms, so a host booted inside a dialog's scale-in would be backed for\n // 96% of its box and stretched by CSS once the animation ended. The observer's\n // first entry follows with the exact, sub-pixel size. Observer entries arrive\n // after the frame's own callbacks and before its paint, so a frame asked for\n // here would leave this one blank, and a resize drag would flicker: it draws\n // at once. Without an observer the box is what it was at boot.\n const hostEl = this.host.nativeElement;\n this.fit(hostEl.clientWidth, hostEl.clientHeight);\n const ro =\n typeof ResizeObserver === 'undefined'\n ? null\n : new ResizeObserver(entries => {\n const rect = entries[entries.length - 1]?.contentRect;\n if (rect && this.fit(rect.width, rect.height)) this.draw();\n });\n ro?.observe(hostEl);\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. The canvas rather than the\n // host, which fills the same box, so a canvas hidden under forced colours stops\n // it too. Without an observer (jsdom) the host simply counts as visible. A box flush\n // against a zero-height clip intersects with nothing showing, and only a second\n // threshold reports the clip moving. It sits below any real ratio, or a clip closing\n // on a transition 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(canvas);\n\n this.destroyRef.onDestroy(() => {\n ro?.disconnect();\n io?.disconnect();\n // These first: losing the context below fires the event they listen for.\n canvas.removeEventListener('webglcontextlost', onLost);\n canvas.removeEventListener('webglcontextrestored', onRestored);\n this.release(gl);\n });\n if (this.gpu) this.booted.set(true);\n }\n\n /**\n * Compile, link and upload what one context needs, and make it current. Returns\n * `null` when it cannot: on a lost context, where every call answers no and\n * nothing is wrong with the shader, it says nothing.\n */\n private build(gl: WebGL2RenderingContext): Gpu | null {\n const vert = this.compile(gl, gl.VERTEX_SHADER, VERT);\n const frag = this.compile(gl, gl.FRAGMENT_SHADER, FRAG);\n const program = vert && frag ? gl.createProgram() : null;\n if (vert && frag && program) {\n gl.attachShader(program, vert);\n gl.attachShader(program, frag);\n gl.linkProgram(program);\n }\n // Only flagged: a shader stays alive as long as a program it is attached to.\n gl.deleteShader(vert);\n gl.deleteShader(frag);\n if (!program) return null;\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n if (!gl.isContextLost()) {\n // eslint-disable-next-line no-console -- GPU shader failures are otherwise invisible\n console.error('wr-iridescence program error:', gl.getProgramInfoLog(program));\n }\n gl.deleteProgram(program);\n return null;\n }\n gl.useProgram(program);\n\n // One oversized triangle covers the viewport, with no index buffer.\n const buffer = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);\n const position = gl.getAttribLocation(program, 'position');\n gl.enableVertexAttribArray(position);\n gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);\n\n return {\n program,\n buffer,\n resolution: gl.getUniformLocation(program, 'uResolution'),\n phase: gl.getUniformLocation(program, 'uPhase'),\n pointer: gl.getUniformLocation(program, 'uPointer'),\n color: gl.getUniformLocation(program, 'uColor'),\n };\n }\n\n private compile(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader | null {\n const shader = gl.createShader(type);\n if (!shader) return null;\n gl.shaderSource(shader, source);\n gl.compileShader(shader);\n if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) return shader;\n if (!gl.isContextLost()) {\n // eslint-disable-next-line no-console -- GPU shader failures are otherwise invisible\n console.error('wr-iridescence shader error:', gl.getShaderInfoLog(shader));\n }\n gl.deleteShader(shader);\n return null;\n }\n\n /**\n * Give the GPU resources back at once rather than to the garbage collector:\n * browsers keep a small number of live contexts and kill the oldest, so a\n * navigating single-page app would lose the context of a page still on screen.\n * Chromium frees the slot here too. WebKit holds it until the context is collected,\n * and no API frees it sooner, so a still or parked host can still be evicted there.\n */\n private release(gl: WebGL2RenderingContext): void {\n if (this.gpu) {\n gl.deleteProgram(this.gpu.program);\n gl.deleteBuffer(this.gpu.buffer);\n }\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n this.gpu = null;\n this.gl = null;\n }\n\n /**\n * Back the canvas for a `width` × `height` box, in CSS pixels. Returns whether\n * anything changed: writing `canvas.width` clears the bitmap even to the value it\n * already has, so an unchanged size is left alone. Rounded down, because rounding\n * to nearest could carry a box back over the budget it was just scaled under.\n */\n private fit(width: number, height: number): boolean {\n const area = width * height;\n const scale = area > MAX_PIXELS ? Math.sqrt(MAX_PIXELS / area) : 1;\n const bitmapWidth = Math.floor(width * scale);\n const bitmapHeight = Math.floor(height * scale);\n if (bitmapWidth === this.bitmap.width && bitmapHeight === this.bitmap.height) return false;\n\n this.bitmap = { width: bitmapWidth, height: bitmapHeight };\n this.hasBox.set(bitmapWidth > 0 && bitmapHeight > 0);\n const canvas = this.canvasRef().nativeElement;\n canvas.width = bitmapWidth;\n canvas.height = bitmapHeight;\n return true;\n }\n\n // Frame loop\n\n /** Start the folds, 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 — a\n // held or polyfilled scheduler does not always honour the cancel — and a late\n // 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 // A phase is stepped by the time that really passed, which is what keeps a\n // 120 Hz screen from running the folds twice as fast. A timestamp that did\n // not advance counts as one 60 Hz frame, and so does the first frame of a\n // run, which has nothing to measure from.\n const elapsed = now - last;\n const step = last < 0 || elapsed <= 0 ? FRAME_MS : Math.min(MAX_FRAME_MS, elapsed);\n last = now;\n const turned = this.turn(step);\n const stirred = this.easePointer(step);\n // At `speed` 0 with the pointer at rest this frame would only repeat the last.\n if (this.dirty || turned || stirred) this.draw();\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 * Step the phases by `step` ms, and return whether they moved. Stepped rather\n * than computed from a clock: a clock times `speed` is a different angle the\n * moment `speed` changes, so moving the input would jump the picture, and here\n * it only changes how far the next step goes.\n */\n private turn(step: number): boolean {\n const speed = this.speed();\n if (speed === 0) return false;\n const seconds = step / 1000;\n for (let i = 0; i < RATE.length; i++) this.phase[i] = wrap(this.phase[i] + seconds * RATE[i] * speed);\n return true;\n }\n\n /**\n * Follow the pointer. On the parent, not the host: content in front of the canvas\n * receives the pointer, and its events bubble to the parent, never to a sibling.\n * Placed directly in a shadow root it has none, and the shadow host is where those\n * composed events land. Never the host itself, which takes no pointer events and\n * so could never hear one.\n */\n private listenToPointer(): () => void {\n const hostEl = this.host.nativeElement;\n const root = hostEl.getRootNode();\n const target =\n hostEl.parentElement ?? (root instanceof ShadowRoot && root.host instanceof HTMLElement ? root.host : null);\n\n const onMove = (event: PointerEvent): void => {\n this.lastMove = { x: event.clientX, y: event.clientY };\n };\n const onLeave = (): void => this.aimAtRest();\n target?.addEventListener('pointermove', onMove, { passive: true });\n target?.addEventListener('pointerleave', onLeave, { passive: true });\n target?.addEventListener('pointercancel', onLeave, { passive: true });\n\n return () => {\n target?.removeEventListener('pointermove', onMove);\n target?.removeEventListener('pointerleave', onLeave);\n target?.removeEventListener('pointercancel', onLeave);\n this.aimAtRest();\n };\n }\n\n private aimAtRest(): void {\n this.lastMove = null;\n this.pointer.aimX = 0;\n this.pointer.aimY = 0;\n }\n\n /**\n * Bring the pointer towards its target by `step` ms, and return whether it moved.\n *\n * The rect is read here, at most once a frame, rather than on every event, and it\n * is the box as drawn, after any transform on an ancestor, so a pointer lands where\n * it is seen. The offset is relative to the box, so a transform needs nothing more.\n */\n private easePointer(step: number): boolean {\n const pointer = this.pointer;\n if (this.lastMove) {\n const rect = this.host.nativeElement.getBoundingClientRect();\n const u = rect.width > 0 ? clamp((this.lastMove.x - rect.left) / rect.width, 0, 1) : 0.5;\n const v = rect.height > 0 ? clamp((this.lastMove.y - rect.top) / rect.height, 0, 1) : 0.5;\n // `v` runs down the box and GL's y runs up it.\n pointer.aimX = (u - 0.5) * 2 * REACH;\n pointer.aimY = (0.5 - v) * 2 * REACH;\n this.lastMove = null;\n }\n\n const amplitude = this.amplitude();\n const targetX = pointer.aimX * amplitude;\n const targetY = pointer.aimY * amplitude;\n // An amplitude that is not finite would stick a `NaN` in `uPointer` for good, since\n // every later step adds to it. 0 times `Infinity` is one, at the box's centre.\n if (!Number.isFinite(targetX) || !Number.isFinite(targetY)) return false;\n if (pointer.x === targetX && pointer.y === targetY) return false;\n\n const share = 1 - Math.exp(-step / POINTER_TAU_MS);\n pointer.x += (targetX - pointer.x) * share;\n pointer.y += (targetY - pointer.y) * share;\n if (Math.abs(targetX - pointer.x) < POINTER_EPSILON && Math.abs(targetY - pointer.y) < POINTER_EPSILON) {\n pointer.x = targetX;\n pointer.y = targetY;\n }\n return true;\n }\n\n // Drawing\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) {\n this.dirty = true;\n return;\n }\n // Nothing is following the pointer now, and there is no loop to ease it back.\n this.aimAtRest();\n this.pointer.x = 0;\n this.pointer.y = 0;\n this.draw();\n }\n\n /**\n * One frame at the current phases and pointer. Leaves `dirty` set when it has\n * nothing to draw on, a lost context or an empty box, so the next chance draws.\n */\n private draw(): void {\n const { gl, gpu } = this;\n const { width, height } = this.bitmap;\n if (!gl || !gpu || width === 0 || height === 0) return;\n\n const [r, g, b] = this.tint();\n gl.viewport(0, 0, width, height);\n gl.uniform2f(gpu.resolution, width, height);\n gl.uniform3f(gpu.phase, this.phase[0], this.phase[1], this.phase[2]);\n gl.uniform2f(gpu.pointer, this.pointer.x, this.pointer.y);\n gl.uniform3f(gpu.color, r, g, b);\n gl.drawArrays(gl.TRIANGLES, 0, 3);\n\n this.dirty = false;\n // Outside Angular, so the `set` schedules the one host-binding update it needs.\n if (!this.painted()) this.painted.set(true);\n }\n\n /**\n * The tint as shader channels. Resolved through the cascade on every draw, since\n * a uniform reads neither a `var()` nor a `light-dark()` and a hook changed by a\n * rule on the host should reach the next frame, and parsed only when that string\n * changes.\n */\n private tint(): readonly [number, number, number] {\n const canvas = this.canvasRef().nativeElement;\n const resolved = resolveCssColor(canvas, this.expression(), FALLBACK_COLOR);\n if (resolved !== this.resolved) {\n this.resolved = resolved;\n this.channels = resolveCssColorChannels(canvas, resolved, FALLBACK_COLOR);\n }\n return this.channels;\n }\n}\n"],"mappings":"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"}
|
|
@@ -100,7 +100,7 @@ var WrMarqueeHarness = class WrMarqueeHarness extends ComponentHarness {
|
|
|
100
100
|
return (await this.host()).hasClass("wr-marquee--fade");
|
|
101
101
|
}
|
|
102
102
|
async getFadeColor() {
|
|
103
|
-
return this.hostStyle("--wr-marquee-fade-color");
|
|
103
|
+
return this.hostStyle("--wr-marquee-input-fade-color");
|
|
104
104
|
}
|
|
105
105
|
async hasScaleOnHover() {
|
|
106
106
|
return (await this.host()).hasClass("wr-marquee--scale-hover");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-bits-marquee-testing.mjs","names":[],"sources":["../../../projects/lib/bits/marquee/testing/wr-marquee-item-harness.ts","../../../projects/lib/bits/marquee/testing/wr-marquee-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 { WrMarqueeItemHarnessFilters } from './types';\n\n/**\n * Test harness for one entry of a `<wr-marquee>`.\n *\n * **Reach these through `WrMarqueeHarness.getItems()`, which scopes them to the first\n * copy.** The strip duplicates its whole sequence to make the loop seamless, so asking\n * the document loader for all of these returns one per entry per copy — at least twice\n * everything, and a count assertion that quietly multiplies. The parent harness is the\n * only place that knows which copy is the original.\n *\n * An entry is built from an image or from a projected template, and it may or may not be\n * a link. That is the reason to ask a harness rather than the DOM: the same question —\n * what does this announce, is it decoration — is answered from a different attribute in\n * each of those shapes, and {@link getAccessibleName} and {@link isDecorative} pick the\n * right one.\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrMarqueeItemHarness extends ComponentHarness {\n static hostSelector = '.wr-marquee__item';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrMarqueeItemHarnessFilters = {}): HarnessPredicate<WrMarqueeItemHarness> {\n return new HarnessPredicate(WrMarqueeItemHarness, options)\n .addOption('name', options.name, (harness, name) =>\n HarnessPredicate.stringMatches(harness.getAccessibleName(), name)\n )\n .addOption('link', options.link, async (harness, link) => (await harness.isLink()) === link);\n }\n\n private readonly link = this.locatorForOptional('a.wr-marquee__link');\n private readonly image = this.locatorForOptional('img');\n private readonly node = this.locatorForOptional('.wr-marquee__node');\n\n /**\n * What this entry announces, without the caller needing to know how it was built.\n *\n * A linked entry answers with the link's own name, which is the end of a four-step\n * ladder — the item's `ariaLabel`, then its `title`, then an image's `alt`, then the\n * catalog's `marquee.link`. An unlinked image answers with its `alt`, where `''` means\n * the logo is decoration and announces nothing. Anything else answers with its text.\n */\n async getAccessibleName(): Promise<string | null> {\n const link = await this.link();\n if (link) return link.getAttribute('aria-label');\n\n const image = await this.image();\n if (image) return image.getAttribute('alt');\n\n return (await this.host()).text();\n }\n\n /** Whether this entry became a link — `[items]` carries `href` per entry, optionally. */\n async isLink(): Promise<boolean> {\n return (await this.link()) !== null;\n }\n\n /** The `href` exactly as the entry declared it, unresolved, or `null` when it is not a link. */\n async getHref(): Promise<string | null> {\n const link = await this.link();\n return link ? link.getAttribute('href') : null;\n }\n\n /**\n * Whether the link opens in a new tab AND says so safely: `target=\"_blank\"` with both\n * `noreferrer` and `noopener` in `rel`.\n *\n * One question rather than two, because half of it is a reverse-tabnabbing hole that\n * nothing visual would ever show. The library hard-codes the safe pair, so this is a\n * regression check on the template rather than on the caller's data.\n *\n * Throws for an entry that is not a link, where a `false` would read as \"it opens in\n * this tab\" — an answer about safety that is not true of anything.\n */\n async opensInNewTab(): Promise<boolean> {\n const link = await this.link();\n if (!link) {\n throw new Error(\n `WrMarqueeItemHarness.opensInNewTab(): \"${await this.getAccessibleName()}\" is not a link — its entry in ` +\n '[items] carries no `href`, so there is no target or rel to check. Ask isLink() first.'\n );\n }\n\n const rel = (await link.getAttribute('rel')) ?? '';\n return (await link.getAttribute('target')) === '_blank' && rel.includes('noreferrer') && rel.includes('noopener');\n }\n\n /**\n * Whether this entry's content is kept out of the accessible tree, leaving whatever\n * wraps it to do the announcing.\n *\n * The marker depends on how the entry was built, which is the whole reason to ask here:\n * a projected template inside a named link carries `aria-hidden`, so the link's own name\n * is not read twice, while an image says the same thing with `alt=\"\"`. It describes the\n * CONTENT — a decorative logo inside a link is still announced, by the link, and that\n * name is {@link getAccessibleName}.\n *\n * Throws for an entry that rendered neither, which should not happen — every entry of\n * `[items]` is one or the other.\n */\n async isDecorative(): Promise<boolean> {\n const node = await this.node();\n if (node) return (await node.getAttribute('aria-hidden')) === 'true';\n\n const image = await this.image();\n if (image) return (await image.getAttribute('alt')) === '';\n\n throw new Error(\n 'WrMarqueeItemHarness.isDecorative(): this entry rendered neither a projected node nor an image, so there ' +\n 'is nothing carrying a decorative marker. Every entry of [items] is one or the other.'\n );\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 { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrMarqueeHarnessFilters, WrMarqueeLink } from './types';\nimport { WrMarqueeItemHarness } from './wr-marquee-item-harness';\n\n/** Copy 0 is the original sequence; every other `.wr-marquee__list` is a clone of it. */\nconst FIRST_COPY = '.wr-marquee__list[data-copy=\"0\"]';\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-marquee>`.\n *\n * **Almost everything worth asserting about a marquee is structural, not animated.** The\n * strip duplicates its sequence so the loop has no seam, and only the first copy is left\n * in the accessible tree — {@link getAnnouncedCopyCount} is the assertion that catches\n * the regression that matters, because a duplicate leaking in makes a screen reader read\n * the whole logo strip twice and changes nothing on screen. The other half is the naming\n * ladder on linked entries, which is real logic with a real bug in its history: the\n * fallback used to be the hard-coded English word \"link\".\n *\n * **Every list query is scoped to the first copy**, and that is the reason to use\n * {@link getItems} rather than a raw `.wr-marquee__item` query, which returns at least\n * twice as many elements as there are entries.\n *\n * Nothing about the motion is offered. The track's `translate3d` is written only when the\n * measured sequence is wider than zero, so it never appears in a test at all, and in a\n * browser it is one frame of a loop that is already stale by the time an assertion reads\n * it. `[speed]` and `[direction]` reach the DOM in no form. Neither does the hover state:\n * pausing flips a private signal with no class, attribute or property behind it, so a\n * `hover()` here would fire events that provably change nothing readable. Reduced\n * motion is the one motion state with markup behind it — the strip turns into a\n * scroller with a single copy — and {@link isScrollable} reads it; see `getGap` for\n * what a harness can honestly read off a stylesheet-driven component, and why it\n * reads it where it does.\n *\n * @example\n * ```ts\n * const loader = TestbedHarnessEnvironment.loader(fixture);\n * const strip = await loader.getHarness(WrMarqueeHarness);\n *\n * expect(await strip.getAnnouncedCopyCount()).toBe(1);\n * expect(await strip.getItemCount()).toBe(logos.length);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrMarqueeHarness extends ComponentHarness {\n static hostSelector = 'wr-marquee';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrMarqueeHarnessFilters = {}): HarnessPredicate<WrMarqueeHarness> {\n return new HarnessPredicate(WrMarqueeHarness, options).addOption('ariaLabel', options.ariaLabel, (harness, label) =>\n HarnessPredicate.stringMatches(harness.getAccessibleName(), label)\n );\n }\n\n private readonly copies = this.locatorForAll('.wr-marquee__list');\n\n /**\n * The name the region announces.\n *\n * Pins the i18n ladder end to end: `'Marquee'` with no catalog loaded, the consumer's\n * `[ariaLabel]` when one is given, the localized string under a catalog. A landmark\n * with no name is an axe failure, so this is what keeps the `region` legitimate rather\n * than a decoration a screen-reader user is invited to jump to and finds unnamed.\n */\n async getAccessibleName(): Promise<string | null> {\n return (await this.host()).getAttribute('aria-label');\n }\n\n /**\n * The landmark role the strip claims.\n *\n * Static in the template and one attribute away from being dropped in a refactor, which\n * is exactly why a consumer asserting their page's landmark structure should be able to\n * see it from here.\n */\n async getRole(): Promise<string | null> {\n return (await this.host()).getAttribute('role');\n }\n\n /**\n * How many copies of the sequence are rendered.\n *\n * The seamless loop is built by repeating the whole list until it covers the container,\n * so the count is decided by measurement — which means it stays at the floor of 2 in a\n * test with no layout, whatever the container would really be. Assert `>= 2`; the\n * growth needs a browser. Under reduced motion there is no loop to hide a seam in, and\n * the count is exactly 1.\n */\n async getCopyCount(): Promise<number> {\n return (await this.copies()).length;\n }\n\n /**\n * Whether the strip is a still scroller rather than a running loop — what it becomes\n * under `prefers-reduced-motion`, read off the `.wr-marquee--static` modifier the\n * component sets from the live preference.\n *\n * The class is what the stylesheet scrolls on, so this answers differently for a strip\n * that froze with its items out of reach; whether it actually SCROLLS is layout, which\n * a unit test does not have.\n */\n async isScrollable(): Promise<boolean> {\n return (await this.host()).hasClass('wr-marquee--static');\n }\n\n /**\n * How many copies are left in the accessible tree. Must be exactly 1.\n *\n * The one assertion here that catches a silent regression: every copy after the first\n * carries `aria-hidden=\"true\"`, and losing that makes a screen reader read the whole\n * strip once per copy while the page looks unchanged.\n *\n * Note the first copy carries the literal `aria-hidden=\"false\"` rather than no\n * attribute at all, so this compares against the string `'true'` — testing the\n * attribute for presence would count nothing at all as announced.\n */\n async getAnnouncedCopyCount(): Promise<number> {\n const copies = await this.copies();\n const hidden = await Promise.all(copies.map(copy => copy.getAttribute('aria-hidden')));\n return hidden.filter(value => value !== 'true').length;\n }\n\n /** The entries of the first copy, in order — one per entry of `[items]`. */\n async getItems(): Promise<WrMarqueeItemHarness[]> {\n return this.locatorForAll(WrMarqueeItemHarness.with({ ancestor: FIRST_COPY }))();\n }\n\n /**\n * How many entries the strip drew, counting the first copy only.\n *\n * One per entry of `[items]` — the mapping a consumer's own data pipeline is really\n * being tested for.\n */\n async getItemCount(): Promise<number> {\n return (await this.locatorForAll(`${FIRST_COPY} .wr-marquee__item`)()).length;\n }\n\n /**\n * The `alt` of each image in the first copy, in order.\n *\n * `null` in this list is the thing to look for: it means the attribute is MISSING, which\n * is a serious axe violation and announces the file name. An empty string is the\n * opposite — a deliberately decorative logo — and the template's `alt || ''` is what\n * keeps the two apart. A method typed `string[]` would have had to flatten them.\n */\n async getImageAlts(): Promise<(string | null)[]> {\n const images = await this.locatorForAll(`${FIRST_COPY} img`)();\n return Promise.all(images.map(image => image.getAttribute('alt')));\n }\n\n /**\n * Every linked entry of the first copy: where it goes and what it announces.\n *\n * The name is the interesting half. It walks a ladder ending in the catalog's\n * `marquee.link`, so an unlabelled logo announces a localized word rather than nothing\n * — real component logic rather than an echo of the input.\n */\n async getLinks(): Promise<WrMarqueeLink[]> {\n const links = await this.locatorForAll(`${FIRST_COPY} a.wr-marquee__link`)();\n return Promise.all(\n links.map(async link => ({ href: await link.getAttribute('href'), name: await link.getAttribute('aria-label') }))\n );\n }\n\n /**\n * Whether the edge fades are on.\n *\n * The fades themselves are `::before` / `::after` gradients, which no unit environment\n * can see — so the modifier class is the observable, and it is public API a consumer\n * may be styling against in any case.\n */\n async hasFade(): Promise<boolean> {\n return (await this.host()).hasClass('wr-marquee--fade');\n }\n\n /**\n * The fade colour as the component passed it through — a CSS value such as\n * `'var(--wr-color-success)'`, not a resolved colour — or `null` when none was given.\n *\n * Worth asserting next to {@link hasFade}: a colour with no fade modifier renders\n * nothing at all, which is a consumer mistake this pairing makes visible.\n */\n async getFadeColor(): Promise<string | null> {\n return this.hostStyle('--wr-marquee-fade-color');\n }\n\n /**\n * Whether entries scale up under the pointer.\n *\n * A `:hover` transform, so again the modifier is the only honest read — and it earns\n * its place by doing more than the name suggests: it also changes the strip's vertical\n * padding, so a lost class shifts the layout as well as the hover.\n */\n async hasScaleOnHover(): Promise<boolean> {\n return (await this.host()).hasClass('wr-marquee--scale-hover');\n }\n\n /** The gap between entries in pixels — the seam width the stylesheet reads. */\n async getGap(): Promise<number> {\n return this.pixels('--wr-marquee-gap', 'getGap');\n }\n\n /**\n * The entry height in pixels.\n *\n * It drives the image height AND the entry font size, so a binding that stops arriving\n * collapses the whole strip rather than merely resizing it.\n */\n async getItemHeight(): Promise<number> {\n return this.pixels('--wr-marquee-height', 'getItemHeight');\n }\n\n /**\n * Read a custom property off the host's inline `style`, rather than through\n * `getCssValue()`.\n *\n * Not a jsdom workaround — the opposite. The stylesheet declares its own\n * `--wr-marquee-gap: 32px` and `--wr-marquee-height: 28px` on `.wr-marquee`, so a\n * computed read in a real browser answers with the stylesheet's default when the host\n * binding is the very thing that broke. The inline value is what the component wrote,\n * and nothing else can forge it.\n */\n private async hostStyle(property: string): Promise<string | null> {\n return inlineStyle(await (await this.host()).getAttribute('style'), property);\n }\n\n /**\n * Parse a `<n>px` custom property, refusing anything that lost its unit.\n *\n * The refusal is the point. `[gap]` and `[itemHeight]` are numbers that the host binding\n * turns into lengths, and the stylesheet uses them raw — a bare `32` is an invalid\n * declaration, so the property falls back to the stylesheet default and the strip looks\n * merely a little different. A `parseFloat` would report 32 either way and hide it.\n */\n private async pixels(property: string, method: string): Promise<number> {\n const raw = await this.hostStyle(property);\n const match = /^(-?\\d*\\.?\\d+)px$/.exec(raw ?? '');\n\n if (!match) {\n throw new Error(\n `WrMarqueeHarness.${method}(): \\`${property}\\` reads \"${raw}\" rather than a px length. The stylesheet ` +\n 'uses this value as a raw CSS length, so anything without the unit is an invalid declaration and the ' +\n 'strip silently falls back to the default in the stylesheet.'\n );\n }\n return Number(match[1]);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-bits-marquee-testing.mjs","names":[],"sources":["../../../projects/lib/bits/marquee/testing/wr-marquee-item-harness.ts","../../../projects/lib/bits/marquee/testing/wr-marquee-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 { WrMarqueeItemHarnessFilters } from './types';\n\n/**\n * Test harness for one entry of a `<wr-marquee>`.\n *\n * **Reach these through `WrMarqueeHarness.getItems()`, which scopes them to the first\n * copy.** The strip duplicates its whole sequence to make the loop seamless, so asking\n * the document loader for all of these returns one per entry per copy — at least twice\n * everything, and a count assertion that quietly multiplies. The parent harness is the\n * only place that knows which copy is the original.\n *\n * An entry is built from an image or from a projected template, and it may or may not be\n * a link. That is the reason to ask a harness rather than the DOM: the same question —\n * what does this announce, is it decoration — is answered from a different attribute in\n * each of those shapes, and {@link getAccessibleName} and {@link isDecorative} pick the\n * right one.\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrMarqueeItemHarness extends ComponentHarness {\n static hostSelector = '.wr-marquee__item';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrMarqueeItemHarnessFilters = {}): HarnessPredicate<WrMarqueeItemHarness> {\n return new HarnessPredicate(WrMarqueeItemHarness, options)\n .addOption('name', options.name, (harness, name) =>\n HarnessPredicate.stringMatches(harness.getAccessibleName(), name)\n )\n .addOption('link', options.link, async (harness, link) => (await harness.isLink()) === link);\n }\n\n private readonly link = this.locatorForOptional('a.wr-marquee__link');\n private readonly image = this.locatorForOptional('img');\n private readonly node = this.locatorForOptional('.wr-marquee__node');\n\n /**\n * What this entry announces, without the caller needing to know how it was built.\n *\n * A linked entry answers with the link's own name, which is the end of a four-step\n * ladder — the item's `ariaLabel`, then its `title`, then an image's `alt`, then the\n * catalog's `marquee.link`. An unlinked image answers with its `alt`, where `''` means\n * the logo is decoration and announces nothing. Anything else answers with its text.\n */\n async getAccessibleName(): Promise<string | null> {\n const link = await this.link();\n if (link) return link.getAttribute('aria-label');\n\n const image = await this.image();\n if (image) return image.getAttribute('alt');\n\n return (await this.host()).text();\n }\n\n /** Whether this entry became a link — `[items]` carries `href` per entry, optionally. */\n async isLink(): Promise<boolean> {\n return (await this.link()) !== null;\n }\n\n /** The `href` exactly as the entry declared it, unresolved, or `null` when it is not a link. */\n async getHref(): Promise<string | null> {\n const link = await this.link();\n return link ? link.getAttribute('href') : null;\n }\n\n /**\n * Whether the link opens in a new tab AND says so safely: `target=\"_blank\"` with both\n * `noreferrer` and `noopener` in `rel`.\n *\n * One question rather than two, because half of it is a reverse-tabnabbing hole that\n * nothing visual would ever show. The library hard-codes the safe pair, so this is a\n * regression check on the template rather than on the caller's data.\n *\n * Throws for an entry that is not a link, where a `false` would read as \"it opens in\n * this tab\" — an answer about safety that is not true of anything.\n */\n async opensInNewTab(): Promise<boolean> {\n const link = await this.link();\n if (!link) {\n throw new Error(\n `WrMarqueeItemHarness.opensInNewTab(): \"${await this.getAccessibleName()}\" is not a link — its entry in ` +\n '[items] carries no `href`, so there is no target or rel to check. Ask isLink() first.'\n );\n }\n\n const rel = (await link.getAttribute('rel')) ?? '';\n return (await link.getAttribute('target')) === '_blank' && rel.includes('noreferrer') && rel.includes('noopener');\n }\n\n /**\n * Whether this entry's content is kept out of the accessible tree, leaving whatever\n * wraps it to do the announcing.\n *\n * The marker depends on how the entry was built, which is the whole reason to ask here:\n * a projected template inside a named link carries `aria-hidden`, so the link's own name\n * is not read twice, while an image says the same thing with `alt=\"\"`. It describes the\n * CONTENT — a decorative logo inside a link is still announced, by the link, and that\n * name is {@link getAccessibleName}.\n *\n * Throws for an entry that rendered neither, which should not happen — every entry of\n * `[items]` is one or the other.\n */\n async isDecorative(): Promise<boolean> {\n const node = await this.node();\n if (node) return (await node.getAttribute('aria-hidden')) === 'true';\n\n const image = await this.image();\n if (image) return (await image.getAttribute('alt')) === '';\n\n throw new Error(\n 'WrMarqueeItemHarness.isDecorative(): this entry rendered neither a projected node nor an image, so there ' +\n 'is nothing carrying a decorative marker. Every entry of [items] is one or the other.'\n );\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 { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrMarqueeHarnessFilters, WrMarqueeLink } from './types';\nimport { WrMarqueeItemHarness } from './wr-marquee-item-harness';\n\n/** Copy 0 is the original sequence; every other `.wr-marquee__list` is a clone of it. */\nconst FIRST_COPY = '.wr-marquee__list[data-copy=\"0\"]';\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-marquee>`.\n *\n * **Almost everything worth asserting about a marquee is structural, not animated.** The\n * strip duplicates its sequence so the loop has no seam, and only the first copy is left\n * in the accessible tree — {@link getAnnouncedCopyCount} is the assertion that catches\n * the regression that matters, because a duplicate leaking in makes a screen reader read\n * the whole logo strip twice and changes nothing on screen. The other half is the naming\n * ladder on linked entries, which is real logic with a real bug in its history: the\n * fallback used to be the hard-coded English word \"link\".\n *\n * **Every list query is scoped to the first copy**, and that is the reason to use\n * {@link getItems} rather than a raw `.wr-marquee__item` query, which returns at least\n * twice as many elements as there are entries.\n *\n * Nothing about the motion is offered. The track's `translate3d` is written only when the\n * measured sequence is wider than zero, so it never appears in a test at all, and in a\n * browser it is one frame of a loop that is already stale by the time an assertion reads\n * it. `[speed]` and `[direction]` reach the DOM in no form. Neither does the hover state:\n * pausing flips a private signal with no class, attribute or property behind it, so a\n * `hover()` here would fire events that provably change nothing readable. Reduced\n * motion is the one motion state with markup behind it — the strip turns into a\n * scroller with a single copy — and {@link isScrollable} reads it; see `getGap` for\n * what a harness can honestly read off a stylesheet-driven component, and why it\n * reads it where it does.\n *\n * @example\n * ```ts\n * const loader = TestbedHarnessEnvironment.loader(fixture);\n * const strip = await loader.getHarness(WrMarqueeHarness);\n *\n * expect(await strip.getAnnouncedCopyCount()).toBe(1);\n * expect(await strip.getItemCount()).toBe(logos.length);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrMarqueeHarness extends ComponentHarness {\n static hostSelector = 'wr-marquee';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrMarqueeHarnessFilters = {}): HarnessPredicate<WrMarqueeHarness> {\n return new HarnessPredicate(WrMarqueeHarness, options).addOption('ariaLabel', options.ariaLabel, (harness, label) =>\n HarnessPredicate.stringMatches(harness.getAccessibleName(), label)\n );\n }\n\n private readonly copies = this.locatorForAll('.wr-marquee__list');\n\n /**\n * The name the region announces.\n *\n * Pins the i18n ladder end to end: `'Marquee'` with no catalog loaded, the consumer's\n * `[ariaLabel]` when one is given, the localized string under a catalog. A landmark\n * with no name is an axe failure, so this is what keeps the `region` legitimate rather\n * than a decoration a screen-reader user is invited to jump to and finds unnamed.\n */\n async getAccessibleName(): Promise<string | null> {\n return (await this.host()).getAttribute('aria-label');\n }\n\n /**\n * The landmark role the strip claims.\n *\n * Static in the template and one attribute away from being dropped in a refactor, which\n * is exactly why a consumer asserting their page's landmark structure should be able to\n * see it from here.\n */\n async getRole(): Promise<string | null> {\n return (await this.host()).getAttribute('role');\n }\n\n /**\n * How many copies of the sequence are rendered.\n *\n * The seamless loop is built by repeating the whole list until it covers the container,\n * so the count is decided by measurement — which means it stays at the floor of 2 in a\n * test with no layout, whatever the container would really be. Assert `>= 2`; the\n * growth needs a browser. Under reduced motion there is no loop to hide a seam in, and\n * the count is exactly 1.\n */\n async getCopyCount(): Promise<number> {\n return (await this.copies()).length;\n }\n\n /**\n * Whether the strip is a still scroller rather than a running loop — what it becomes\n * under `prefers-reduced-motion`, read off the `.wr-marquee--static` modifier the\n * component sets from the live preference.\n *\n * The class is what the stylesheet scrolls on, so this answers differently for a strip\n * that froze with its items out of reach; whether it actually SCROLLS is layout, which\n * a unit test does not have.\n */\n async isScrollable(): Promise<boolean> {\n return (await this.host()).hasClass('wr-marquee--static');\n }\n\n /**\n * How many copies are left in the accessible tree. Must be exactly 1.\n *\n * The one assertion here that catches a silent regression: every copy after the first\n * carries `aria-hidden=\"true\"`, and losing that makes a screen reader read the whole\n * strip once per copy while the page looks unchanged.\n *\n * Note the first copy carries the literal `aria-hidden=\"false\"` rather than no\n * attribute at all, so this compares against the string `'true'` — testing the\n * attribute for presence would count nothing at all as announced.\n */\n async getAnnouncedCopyCount(): Promise<number> {\n const copies = await this.copies();\n const hidden = await Promise.all(copies.map(copy => copy.getAttribute('aria-hidden')));\n return hidden.filter(value => value !== 'true').length;\n }\n\n /** The entries of the first copy, in order — one per entry of `[items]`. */\n async getItems(): Promise<WrMarqueeItemHarness[]> {\n return this.locatorForAll(WrMarqueeItemHarness.with({ ancestor: FIRST_COPY }))();\n }\n\n /**\n * How many entries the strip drew, counting the first copy only.\n *\n * One per entry of `[items]` — the mapping a consumer's own data pipeline is really\n * being tested for.\n */\n async getItemCount(): Promise<number> {\n return (await this.locatorForAll(`${FIRST_COPY} .wr-marquee__item`)()).length;\n }\n\n /**\n * The `alt` of each image in the first copy, in order.\n *\n * `null` in this list is the thing to look for: it means the attribute is MISSING, which\n * is a serious axe violation and announces the file name. An empty string is the\n * opposite — a deliberately decorative logo — and the template's `alt || ''` is what\n * keeps the two apart. A method typed `string[]` would have had to flatten them.\n */\n async getImageAlts(): Promise<(string | null)[]> {\n const images = await this.locatorForAll(`${FIRST_COPY} img`)();\n return Promise.all(images.map(image => image.getAttribute('alt')));\n }\n\n /**\n * Every linked entry of the first copy: where it goes and what it announces.\n *\n * The name is the interesting half. It walks a ladder ending in the catalog's\n * `marquee.link`, so an unlabelled logo announces a localized word rather than nothing\n * — real component logic rather than an echo of the input.\n */\n async getLinks(): Promise<WrMarqueeLink[]> {\n const links = await this.locatorForAll(`${FIRST_COPY} a.wr-marquee__link`)();\n return Promise.all(\n links.map(async link => ({ href: await link.getAttribute('href'), name: await link.getAttribute('aria-label') }))\n );\n }\n\n /**\n * Whether the edge fades are on.\n *\n * The fades themselves are `::before` / `::after` gradients, which no unit environment\n * can see — so the modifier class is the observable, and it is public API a consumer\n * may be styling against in any case.\n */\n async hasFade(): Promise<boolean> {\n return (await this.host()).hasClass('wr-marquee--fade');\n }\n\n /**\n * The fade colour as the component passed it through — a CSS value such as\n * `'var(--wr-color-success)'`, not a resolved colour — or `null` when none was given.\n *\n * Worth asserting next to {@link hasFade}: a colour with no fade modifier renders\n * nothing at all, which is a consumer mistake this pairing makes visible.\n */\n async getFadeColor(): Promise<string | null> {\n return this.hostStyle('--wr-marquee-input-fade-color');\n }\n\n /**\n * Whether entries scale up under the pointer.\n *\n * A `:hover` transform, so again the modifier is the only honest read — and it earns\n * its place by doing more than the name suggests: it also changes the strip's vertical\n * padding, so a lost class shifts the layout as well as the hover.\n */\n async hasScaleOnHover(): Promise<boolean> {\n return (await this.host()).hasClass('wr-marquee--scale-hover');\n }\n\n /** The gap between entries in pixels — the seam width the stylesheet reads. */\n async getGap(): Promise<number> {\n return this.pixels('--wr-marquee-gap', 'getGap');\n }\n\n /**\n * The entry height in pixels.\n *\n * It drives the image height AND the entry font size, so a binding that stops arriving\n * collapses the whole strip rather than merely resizing it.\n */\n async getItemHeight(): Promise<number> {\n return this.pixels('--wr-marquee-height', 'getItemHeight');\n }\n\n /**\n * Read a custom property off the host's inline `style`, rather than through\n * `getCssValue()`.\n *\n * Not a jsdom workaround — the opposite. The stylesheet declares its own\n * `--wr-marquee-gap: 32px` and `--wr-marquee-height: 28px` on `.wr-marquee`, so a\n * computed read in a real browser answers with the stylesheet's default when the host\n * binding is the very thing that broke. The inline value is what the component wrote,\n * and nothing else can forge it.\n */\n private async hostStyle(property: string): Promise<string | null> {\n return inlineStyle(await (await this.host()).getAttribute('style'), property);\n }\n\n /**\n * Parse a `<n>px` custom property, refusing anything that lost its unit.\n *\n * The refusal is the point. `[gap]` and `[itemHeight]` are numbers that the host binding\n * turns into lengths, and the stylesheet uses them raw — a bare `32` is an invalid\n * declaration, so the property falls back to the stylesheet default and the strip looks\n * merely a little different. A `parseFloat` would report 32 either way and hide it.\n */\n private async pixels(property: string, method: string): Promise<number> {\n const raw = await this.hostStyle(property);\n const match = /^(-?\\d*\\.?\\d+)px$/.exec(raw ?? '');\n\n if (!match) {\n throw new Error(\n `WrMarqueeHarness.${method}(): \\`${property}\\` reads \"${raw}\" rather than a px length. The stylesheet ` +\n 'uses this value as a raw CSS length, so anything without the unit is an invalid declaration and the ' +\n 'strip silently falls back to the default in the stylesheet.'\n );\n }\n return Number(match[1]);\n }\n}\n"],"mappings":"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"}
|
|
@@ -195,7 +195,7 @@ var WrMarquee = class WrMarquee {
|
|
|
195
195
|
}
|
|
196
196
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
197
197
|
minVersion: "12.0.0",
|
|
198
|
-
version: "22.2.
|
|
198
|
+
version: "22.2.4",
|
|
199
199
|
ngImport: i0,
|
|
200
200
|
type: WrMarquee,
|
|
201
201
|
deps: [],
|
|
@@ -203,7 +203,7 @@ var WrMarquee = class WrMarquee {
|
|
|
203
203
|
});
|
|
204
204
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
205
205
|
minVersion: "17.0.0",
|
|
206
|
-
version: "22.2.
|
|
206
|
+
version: "22.2.4",
|
|
207
207
|
type: WrMarquee,
|
|
208
208
|
isStandalone: true,
|
|
209
209
|
selector: "wr-marquee",
|
|
@@ -292,7 +292,7 @@ var WrMarquee = class WrMarquee {
|
|
|
292
292
|
"class.wr-marquee--scale-hover": "scaleOnHover()",
|
|
293
293
|
"style.--wr-marquee-gap.px": "gap()",
|
|
294
294
|
"style.--wr-marquee-height.px": "itemHeight()",
|
|
295
|
-
"style.--wr-marquee-fade-color": "fadeOutColor() || null",
|
|
295
|
+
"style.--wr-marquee-input-fade-color": "fadeOutColor() || null",
|
|
296
296
|
"class.wr-marquee--static": "isStatic()",
|
|
297
297
|
"attr.role": "\"region\"",
|
|
298
298
|
"attr.aria-label": "resolvedAriaLabel()",
|
|
@@ -309,7 +309,7 @@ var WrMarquee = class WrMarquee {
|
|
|
309
309
|
}],
|
|
310
310
|
ngImport: i0,
|
|
311
311
|
template: "<div class=\"wr-marquee__track\" #track (mouseenter)=\"onEnter()\" (mouseleave)=\"onLeave()\">\n @for (copyIndex of copyIndices(); track copyIndex) {\n <!--\n `inert` alongside `aria-hidden`, and never one without the other: the copies\n exist only to fill the track, and an item with an `href` renders a real anchor\n in every one of them. Hidden from the screen reader but still in the tab order,\n each duplicate is a tab stop that announces nothing — axe's `aria-hidden-focus`,\n and N stops on one destination for anyone using the keyboard.\n -->\n <ul\n class=\"wr-marquee__list\"\n role=\"list\"\n [attr.aria-hidden]=\"copyIndex > 0\"\n [attr.inert]=\"copyIndex > 0 ? '' : null\"\n [attr.data-copy]=\"copyIndex\"\n >\n @for (item of items(); track $index) {\n <li class=\"wr-marquee__item\" role=\"listitem\">\n @if (item.href) {\n <a\n class=\"wr-marquee__link\"\n [href]=\"item.href\"\n [attr.aria-label]=\"\n (isNodeItem(item) ? item.ariaLabel || item.title : item.alt || item.title) || resolvedLinkLabel()\n \"\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n >\n <ng-container *ngTemplateOutlet=\"content; context: { $implicit: item }\" />\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"content; context: { $implicit: item }\" />\n }\n </li>\n }\n </ul>\n }\n</div>\n\n<ng-template #content let-item>\n @if (isNodeItem(item)) {\n <span\n class=\"wr-marquee__node\"\n [attr.title]=\"item.title || null\"\n [attr.aria-hidden]=\"!!item.href && !item.ariaLabel\"\n >\n <ng-container *ngTemplateOutlet=\"item.node\" />\n </span>\n } @else {\n <img\n [src]=\"item.src\"\n [srcset]=\"item.srcSet || null\"\n [sizes]=\"item.sizes || null\"\n [width]=\"item.width || null\"\n [height]=\"item.height || null\"\n [alt]=\"item.alt || ''\"\n [title]=\"item.title || null\"\n loading=\"lazy\"\n decoding=\"async\"\n draggable=\"false\"\n />\n }\n</ng-template>\n",
|
|
312
|
-
styles: [".wr-marquee{--wr-marquee-gap: 32px;--wr-marquee-height: 28px;--wr-marquee-fade-color-auto: var(--wr-color-surface, #ffffff);position:relative;display:block;width:100%;overflow:hidden;direction:ltr}.wr-marquee--scale-hover{padding-top:calc(var(--wr-marquee-height) * .1);padding-bottom:calc(var(--wr-marquee-height) * .1)}.wr-marquee__track{display:flex;width:max-content;will-change:transform;-webkit-user-select:none;user-select:none;position:relative;z-index:0}.wr-marquee__list{display:flex;align-items:center;margin:0;padding:0;list-style:none}.wr-marquee__item{flex:0 0 auto;margin-right:var(--wr-marquee-gap);font-size:var(--wr-marquee-height);line-height:1}.wr-marquee__item:dir(rtl){direction:rtl}.wr-marquee__item:last-child{margin-right:var(--wr-marquee-gap)}.wr-marquee__node{display:inline-flex;align-items:center}.wr-marquee__item img{height:var(--wr-marquee-height);width:auto;display:block;object-fit:contain;image-rendering:-webkit-optimize-contrast;-webkit-user-drag:none;pointer-events:none;transition:transform var(--wr-duration-slow) var(--wr-ease-out)}.wr-marquee--scale-hover .wr-marquee__item{overflow:visible}.wr-marquee--scale-hover .wr-marquee__item:hover img,.wr-marquee--scale-hover .wr-marquee__item:hover .wr-marquee__node{transform:scale(1.2);transform-origin:center center}.wr-marquee--scale-hover .wr-marquee__node{transition:transform var(--wr-duration-slow) var(--wr-ease-out)}.wr-marquee__link{display:inline-flex;align-items:center;text-decoration:none;border-radius:4px;transition:opacity .2s ease}.wr-marquee__link:hover{opacity:.8}.wr-marquee__link:focus-visible{outline:2px solid currentColor;outline-offset:2px}.wr-marquee--fade:before,.wr-marquee--fade:after{content:\"\";position:absolute;top:0;bottom:0;width:clamp(24px,8%,120px);pointer-events:none;z-index:10}.wr-marquee--fade:before{left:0;background:linear-gradient(to right,var(--wr-marquee-fade-color, var(--wr-marquee-fade-color-auto)) 0%,rgba(0,0,0,0) 100%)}.wr-marquee--fade:after{right:0;background:linear-gradient(to left,var(--wr-marquee-fade-color, var(--wr-marquee-fade-color-auto)) 0%,rgba(0,0,0,0) 100%)}.wr-marquee--static{overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain}.wr-marquee--static:focus-visible{outline:var(--wr-focus-ring-width) solid var(--wr-focus-ring-color);outline-offset:calc(var(--wr-focus-ring-width) * -1);box-shadow:inset 0 0 0 calc(var(--wr-focus-ring-width) + var(--wr-focus-ring-offset)) var(--wr-focus-ring-halo)}.wr-marquee--static:before,.wr-marquee--static:after{display:none}@media(prefers-reduced-motion:reduce){.wr-marquee .wr-marquee__track{transform:translateZ(0)!important}.wr-marquee .wr-marquee__item img,.wr-marquee .wr-marquee__node{transition:none!important}}\n"],
|
|
312
|
+
styles: [".wr-marquee{--wr-marquee-gap: 32px;--wr-marquee-height: 28px;--wr-marquee-fade-color-auto: var(--wr-color-surface, #ffffff);--wr-marquee-input-fade-color: initial;position:relative;display:block;width:100%;overflow:hidden;direction:ltr}.wr-marquee--scale-hover{padding-top:calc(var(--wr-marquee-height) * .1);padding-bottom:calc(var(--wr-marquee-height) * .1)}.wr-marquee__track{display:flex;width:max-content;will-change:transform;-webkit-user-select:none;user-select:none;position:relative;z-index:0}.wr-marquee__list{display:flex;align-items:center;margin:0;padding:0;list-style:none}.wr-marquee__item{flex:0 0 auto;margin-right:var(--wr-marquee-gap);font-size:var(--wr-marquee-height);line-height:1}.wr-marquee__item:dir(rtl){direction:rtl}.wr-marquee__item:last-child{margin-right:var(--wr-marquee-gap)}.wr-marquee__node{display:inline-flex;align-items:center}.wr-marquee__item img{height:var(--wr-marquee-height);width:auto;display:block;object-fit:contain;image-rendering:-webkit-optimize-contrast;-webkit-user-drag:none;pointer-events:none;transition:transform var(--wr-duration-slow) var(--wr-ease-out)}.wr-marquee--scale-hover .wr-marquee__item{overflow:visible}.wr-marquee--scale-hover .wr-marquee__item:hover img,.wr-marquee--scale-hover .wr-marquee__item:hover .wr-marquee__node{transform:scale(1.2);transform-origin:center center}.wr-marquee--scale-hover .wr-marquee__node{transition:transform var(--wr-duration-slow) var(--wr-ease-out)}.wr-marquee__link{display:inline-flex;align-items:center;text-decoration:none;border-radius:4px;transition:opacity .2s ease}.wr-marquee__link:hover{opacity:.8}.wr-marquee__link:focus-visible{outline:2px solid currentColor;outline-offset:2px}.wr-marquee--fade:before,.wr-marquee--fade:after{content:\"\";position:absolute;top:0;bottom:0;width:clamp(24px,8%,120px);pointer-events:none;z-index:10}.wr-marquee--fade:before{left:0;background:linear-gradient(to right,var(--wr-marquee-input-fade-color, var(--wr-marquee-fade-color, var(--wr-marquee-fade-color-auto))) 0%,rgba(0,0,0,0) 100%)}.wr-marquee--fade:after{right:0;background:linear-gradient(to left,var(--wr-marquee-input-fade-color, var(--wr-marquee-fade-color, var(--wr-marquee-fade-color-auto))) 0%,rgba(0,0,0,0) 100%)}.wr-marquee--static{overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain}.wr-marquee--static:focus-visible{outline:var(--wr-focus-ring-width) solid var(--wr-focus-ring-color);outline-offset:calc(var(--wr-focus-ring-width) * -1);box-shadow:inset 0 0 0 calc(var(--wr-focus-ring-width) + var(--wr-focus-ring-offset)) var(--wr-focus-ring-halo)}.wr-marquee--static:before,.wr-marquee--static:after{display:none}@media(prefers-reduced-motion:reduce){.wr-marquee .wr-marquee__track{transform:translateZ(0)!important}.wr-marquee .wr-marquee__item img,.wr-marquee .wr-marquee__node{transition:none!important}}\n"],
|
|
313
313
|
dependencies: [{
|
|
314
314
|
kind: "directive",
|
|
315
315
|
type: NgTemplateOutlet,
|
|
@@ -325,7 +325,7 @@ var WrMarquee = class WrMarquee {
|
|
|
325
325
|
};
|
|
326
326
|
i0.ɵɵngDeclareClassMetadata({
|
|
327
327
|
minVersion: "12.0.0",
|
|
328
|
-
version: "22.2.
|
|
328
|
+
version: "22.2.4",
|
|
329
329
|
ngImport: i0,
|
|
330
330
|
type: WrMarquee,
|
|
331
331
|
decorators: [{
|
|
@@ -340,14 +340,14 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
340
340
|
"[class.wr-marquee--scale-hover]": "scaleOnHover()",
|
|
341
341
|
"[style.--wr-marquee-gap.px]": "gap()",
|
|
342
342
|
"[style.--wr-marquee-height.px]": "itemHeight()",
|
|
343
|
-
"[style.--wr-marquee-fade-color]": "fadeOutColor() || null",
|
|
343
|
+
"[style.--wr-marquee-input-fade-color]": "fadeOutColor() || null",
|
|
344
344
|
"[class.wr-marquee--static]": "isStatic()",
|
|
345
345
|
"[attr.role]": "\"region\"",
|
|
346
346
|
"[attr.aria-label]": "resolvedAriaLabel()",
|
|
347
347
|
"[attr.tabindex]": "scrollerTabIndex()"
|
|
348
348
|
},
|
|
349
349
|
template: "<div class=\"wr-marquee__track\" #track (mouseenter)=\"onEnter()\" (mouseleave)=\"onLeave()\">\n @for (copyIndex of copyIndices(); track copyIndex) {\n <!--\n `inert` alongside `aria-hidden`, and never one without the other: the copies\n exist only to fill the track, and an item with an `href` renders a real anchor\n in every one of them. Hidden from the screen reader but still in the tab order,\n each duplicate is a tab stop that announces nothing — axe's `aria-hidden-focus`,\n and N stops on one destination for anyone using the keyboard.\n -->\n <ul\n class=\"wr-marquee__list\"\n role=\"list\"\n [attr.aria-hidden]=\"copyIndex > 0\"\n [attr.inert]=\"copyIndex > 0 ? '' : null\"\n [attr.data-copy]=\"copyIndex\"\n >\n @for (item of items(); track $index) {\n <li class=\"wr-marquee__item\" role=\"listitem\">\n @if (item.href) {\n <a\n class=\"wr-marquee__link\"\n [href]=\"item.href\"\n [attr.aria-label]=\"\n (isNodeItem(item) ? item.ariaLabel || item.title : item.alt || item.title) || resolvedLinkLabel()\n \"\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n >\n <ng-container *ngTemplateOutlet=\"content; context: { $implicit: item }\" />\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"content; context: { $implicit: item }\" />\n }\n </li>\n }\n </ul>\n }\n</div>\n\n<ng-template #content let-item>\n @if (isNodeItem(item)) {\n <span\n class=\"wr-marquee__node\"\n [attr.title]=\"item.title || null\"\n [attr.aria-hidden]=\"!!item.href && !item.ariaLabel\"\n >\n <ng-container *ngTemplateOutlet=\"item.node\" />\n </span>\n } @else {\n <img\n [src]=\"item.src\"\n [srcset]=\"item.srcSet || null\"\n [sizes]=\"item.sizes || null\"\n [width]=\"item.width || null\"\n [height]=\"item.height || null\"\n [alt]=\"item.alt || ''\"\n [title]=\"item.title || null\"\n loading=\"lazy\"\n decoding=\"async\"\n draggable=\"false\"\n />\n }\n</ng-template>\n",
|
|
350
|
-
styles: [".wr-marquee{--wr-marquee-gap: 32px;--wr-marquee-height: 28px;--wr-marquee-fade-color-auto: var(--wr-color-surface, #ffffff);position:relative;display:block;width:100%;overflow:hidden;direction:ltr}.wr-marquee--scale-hover{padding-top:calc(var(--wr-marquee-height) * .1);padding-bottom:calc(var(--wr-marquee-height) * .1)}.wr-marquee__track{display:flex;width:max-content;will-change:transform;-webkit-user-select:none;user-select:none;position:relative;z-index:0}.wr-marquee__list{display:flex;align-items:center;margin:0;padding:0;list-style:none}.wr-marquee__item{flex:0 0 auto;margin-right:var(--wr-marquee-gap);font-size:var(--wr-marquee-height);line-height:1}.wr-marquee__item:dir(rtl){direction:rtl}.wr-marquee__item:last-child{margin-right:var(--wr-marquee-gap)}.wr-marquee__node{display:inline-flex;align-items:center}.wr-marquee__item img{height:var(--wr-marquee-height);width:auto;display:block;object-fit:contain;image-rendering:-webkit-optimize-contrast;-webkit-user-drag:none;pointer-events:none;transition:transform var(--wr-duration-slow) var(--wr-ease-out)}.wr-marquee--scale-hover .wr-marquee__item{overflow:visible}.wr-marquee--scale-hover .wr-marquee__item:hover img,.wr-marquee--scale-hover .wr-marquee__item:hover .wr-marquee__node{transform:scale(1.2);transform-origin:center center}.wr-marquee--scale-hover .wr-marquee__node{transition:transform var(--wr-duration-slow) var(--wr-ease-out)}.wr-marquee__link{display:inline-flex;align-items:center;text-decoration:none;border-radius:4px;transition:opacity .2s ease}.wr-marquee__link:hover{opacity:.8}.wr-marquee__link:focus-visible{outline:2px solid currentColor;outline-offset:2px}.wr-marquee--fade:before,.wr-marquee--fade:after{content:\"\";position:absolute;top:0;bottom:0;width:clamp(24px,8%,120px);pointer-events:none;z-index:10}.wr-marquee--fade:before{left:0;background:linear-gradient(to right,var(--wr-marquee-fade-color, var(--wr-marquee-fade-color-auto)) 0%,rgba(0,0,0,0) 100%)}.wr-marquee--fade:after{right:0;background:linear-gradient(to left,var(--wr-marquee-fade-color, var(--wr-marquee-fade-color-auto)) 0%,rgba(0,0,0,0) 100%)}.wr-marquee--static{overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain}.wr-marquee--static:focus-visible{outline:var(--wr-focus-ring-width) solid var(--wr-focus-ring-color);outline-offset:calc(var(--wr-focus-ring-width) * -1);box-shadow:inset 0 0 0 calc(var(--wr-focus-ring-width) + var(--wr-focus-ring-offset)) var(--wr-focus-ring-halo)}.wr-marquee--static:before,.wr-marquee--static:after{display:none}@media(prefers-reduced-motion:reduce){.wr-marquee .wr-marquee__track{transform:translateZ(0)!important}.wr-marquee .wr-marquee__item img,.wr-marquee .wr-marquee__node{transition:none!important}}\n"]
|
|
350
|
+
styles: [".wr-marquee{--wr-marquee-gap: 32px;--wr-marquee-height: 28px;--wr-marquee-fade-color-auto: var(--wr-color-surface, #ffffff);--wr-marquee-input-fade-color: initial;position:relative;display:block;width:100%;overflow:hidden;direction:ltr}.wr-marquee--scale-hover{padding-top:calc(var(--wr-marquee-height) * .1);padding-bottom:calc(var(--wr-marquee-height) * .1)}.wr-marquee__track{display:flex;width:max-content;will-change:transform;-webkit-user-select:none;user-select:none;position:relative;z-index:0}.wr-marquee__list{display:flex;align-items:center;margin:0;padding:0;list-style:none}.wr-marquee__item{flex:0 0 auto;margin-right:var(--wr-marquee-gap);font-size:var(--wr-marquee-height);line-height:1}.wr-marquee__item:dir(rtl){direction:rtl}.wr-marquee__item:last-child{margin-right:var(--wr-marquee-gap)}.wr-marquee__node{display:inline-flex;align-items:center}.wr-marquee__item img{height:var(--wr-marquee-height);width:auto;display:block;object-fit:contain;image-rendering:-webkit-optimize-contrast;-webkit-user-drag:none;pointer-events:none;transition:transform var(--wr-duration-slow) var(--wr-ease-out)}.wr-marquee--scale-hover .wr-marquee__item{overflow:visible}.wr-marquee--scale-hover .wr-marquee__item:hover img,.wr-marquee--scale-hover .wr-marquee__item:hover .wr-marquee__node{transform:scale(1.2);transform-origin:center center}.wr-marquee--scale-hover .wr-marquee__node{transition:transform var(--wr-duration-slow) var(--wr-ease-out)}.wr-marquee__link{display:inline-flex;align-items:center;text-decoration:none;border-radius:4px;transition:opacity .2s ease}.wr-marquee__link:hover{opacity:.8}.wr-marquee__link:focus-visible{outline:2px solid currentColor;outline-offset:2px}.wr-marquee--fade:before,.wr-marquee--fade:after{content:\"\";position:absolute;top:0;bottom:0;width:clamp(24px,8%,120px);pointer-events:none;z-index:10}.wr-marquee--fade:before{left:0;background:linear-gradient(to right,var(--wr-marquee-input-fade-color, var(--wr-marquee-fade-color, var(--wr-marquee-fade-color-auto))) 0%,rgba(0,0,0,0) 100%)}.wr-marquee--fade:after{right:0;background:linear-gradient(to left,var(--wr-marquee-input-fade-color, var(--wr-marquee-fade-color, var(--wr-marquee-fade-color-auto))) 0%,rgba(0,0,0,0) 100%)}.wr-marquee--static{overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain}.wr-marquee--static:focus-visible{outline:var(--wr-focus-ring-width) solid var(--wr-focus-ring-color);outline-offset:calc(var(--wr-focus-ring-width) * -1);box-shadow:inset 0 0 0 calc(var(--wr-focus-ring-width) + var(--wr-focus-ring-offset)) var(--wr-focus-ring-halo)}.wr-marquee--static:before,.wr-marquee--static:after{display:none}@media(prefers-reduced-motion:reduce){.wr-marquee .wr-marquee__track{transform:translateZ(0)!important}.wr-marquee .wr-marquee__item img,.wr-marquee .wr-marquee__node{transition:none!important}}\n"]
|
|
351
351
|
}]
|
|
352
352
|
}],
|
|
353
353
|
ctorParameters: () => [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-bits-marquee.mjs","names":[],"sources":["../../../projects/lib/bits/marquee/marquee.ts","../../../projects/lib/bits/marquee/marquee.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 LogoLoop effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/animations/logo-loop\n *\n * Renamed `wr-marquee` in ngwr — same rAF-driven infinite carousel,\n * supports image + template items, optional fade/scale/pause-on-hover.\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport { NgTemplateOutlet, isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n afterRenderEffect,\n computed,\n effect,\n inject,\n input,\n signal,\n viewChild,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrMarqueeNode, WrMarqueeItem } from './types';\n\nconst SMOOTH_TAU = 0.25;\nconst MIN_COPIES = 2;\nconst COPY_HEADROOM = 2;\n\nfunction isNode(item: WrMarqueeItem): item is WrMarqueeNode {\n return 'node' in item;\n}\n\n/**\n * What takes a tab stop of its own inside an item. Not `getFocusableElements()`,\n * which drops anything without a layout box: whether the strip already has a\n * stop is a question about its markup, and it is asked of the one copy left.\n */\nconst FOCUSABLE =\n 'a[href], area[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), ' +\n 'select:not([disabled]), textarea:not([disabled]), iframe, [contenteditable]:not([contenteditable=\"false\"]), ' +\n '[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Endless horizontal marquee. Auto-duplicates the items list until it\n * covers the container width, then animates the track with a smooth\n * rAF-driven scroll. Optional edge fades, scale-on-hover, and pause /\n * variable-speed hover behaviour.\n *\n * Under `prefers-reduced-motion` the strip stops moving and becomes a plain\n * horizontal scroller instead: one copy of the items, no edge fades, and a tab\n * stop on the region itself when no item takes one, so every item stays in\n * reach of a pointer and of the keyboard.\n *\n * @example\n * ```html\n * <wr-marquee\n * [items]=\"logos\"\n * [speed]=\"80\"\n * [gap]=\"48\"\n * [itemHeight]=\"36\"\n * [fadeOut]=\"true\"\n * [scaleOnHover]=\"true\"\n * [pauseOnHover]=\"true\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/animations/logo-loop\n */\n@Component({\n selector: 'wr-marquee',\n templateUrl: './marquee.html',\n styleUrl: './marquee.scss',\n encapsulation: ViewEncapsulation.None,\n imports: [NgTemplateOutlet],\n host: {\n class: 'wr-marquee',\n '[class.wr-marquee--fade]': 'fadeOut()',\n '[class.wr-marquee--scale-hover]': 'scaleOnHover()',\n '[style.--wr-marquee-gap.px]': 'gap()',\n '[style.--wr-marquee-height.px]': 'itemHeight()',\n '[style.--wr-marquee-fade-color]': 'fadeOutColor() || null',\n '[class.wr-marquee--static]': 'isStatic()',\n '[attr.role]': '\"region\"',\n '[attr.aria-label]': 'resolvedAriaLabel()',\n '[attr.tabindex]': 'scrollerTabIndex()',\n },\n})\nexport class WrMarquee {\n /** Items to display. Each entry is either an image source or a TemplateRef node. */\n readonly items = input.required<readonly WrMarqueeItem[]>();\n\n /** Scroll speed in pixels per second. Negative reverses direction. @default 120 */\n readonly speed = input(120, { transform: numAttr(120) });\n\n /** Track direction. @default 'left' */\n readonly direction = input<'left' | 'right'>('left');\n\n /** Item height in pixels. @default 28 */\n readonly itemHeight = input(28, { transform: numAttr(28) });\n\n /** Gap between items in pixels. @default 32 */\n readonly gap = input(32, { transform: numAttr(32) });\n\n /** Pause the loop on hover. @default false */\n readonly pauseOnHover = input(false, { transform: coerceBooleanProperty });\n\n /** Custom speed (px/s) when hovered. Overrides `pauseOnHover`. */\n readonly hoverSpeed = input<number | undefined>(undefined, {\n transform: (v: unknown): number | undefined => (v == null || v === '' ? undefined : coerceNumberProperty(v, 0)),\n });\n\n /** Apply edge fade-out gradients. @default false */\n readonly fadeOut = input(false, { transform: coerceBooleanProperty });\n\n /** CSS colour for the fade-out gradient. Defaults to the page background. */\n readonly fadeOutColor = input<string>('');\n\n /** Scale individual items up on hover. @default false */\n readonly scaleOnHover = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Accessible name for the region. Falls back to `marquee.label`, then\n * `'Marquee'`.\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'marquee.label', 'Marquee');\n\n /**\n * Name used for an item that links somewhere but carries no text of its own.\n * Falls back to `marquee.link`, then `'link'` — a bare `'link'` in English was\n * what a screen reader announced for every unlabelled logo in the strip.\n */\n protected readonly resolvedLinkLabel = useI18nText(signal(null).asReadonly(), 'marquee.link', 'link');\n\n private readonly trackEl = viewChild.required<ElementRef<HTMLElement>>('track');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n /** Flips once the first render has been measured — the loop waits for it. */\n private readonly ready = signal(false);\n /** Whether the host is in the viewport; stays `true` without an observer. */\n private readonly onScreen = signal(true);\n /** Loop state, kept across a park so the strip resumes where it stopped. */\n private offset = 0;\n private velocity = 0;\n\n protected readonly seqWidth = signal(0);\n protected readonly copyCount = signal(MIN_COPIES);\n protected readonly isHovered = signal(false);\n\n /**\n * Reduced motion, read live. The loop stops, and a frozen strip would leave\n * every item past the edge out of reach — so the host scrolls instead\n * (`.wr-marquee--static`), and the duplicate copies, which exist only to hide\n * the seam of a loop, are not rendered.\n */\n protected readonly isStatic = this.platform.prefersReducedMotion;\n\n protected readonly copyIndices = computed(() =>\n this.isStatic() ? [0] : Array.from({ length: this.copyCount() }, (_, i) => i)\n );\n\n /** Whether an item already takes a tab stop, which the keyboard can scroll the strip to. */\n private readonly hasFocusableItem = signal(false);\n\n /**\n * The static strip is a scroll container, and one the keyboard cannot reach\n * cannot be scrolled from it. A focused link scrolls itself into view, so the\n * region takes a stop of its own only when no item has one. It carries the\n * region's name.\n */\n protected readonly scrollerTabIndex = computed(() => (this.isStatic() && !this.hasFocusableItem() ? 0 : null));\n\n /** Resolved hover speed: `hoverSpeed` overrides, else 0 when `pauseOnHover`, else live. */\n private readonly effectiveHoverSpeed = computed<number | undefined>(() => {\n const explicit = this.hoverSpeed();\n if (explicit !== undefined) return explicit;\n return this.pauseOnHover() ? 0 : undefined;\n });\n\n /** Target velocity in px/s, sign baked in. */\n private readonly targetVelocity = computed(() => {\n const s = this.speed();\n const sign = this.direction() === 'left' ? 1 : -1;\n return Math.abs(s) * sign * (s < 0 ? -1 : 1);\n });\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n this.measure();\n this.watchResize();\n this.watchImageLoads();\n this.watchVisibility();\n this.ready.set(true);\n });\n\n // The loop runs only while there is something to see: never under\n // `prefers-reduced-motion` (read live — the stylesheet pins the track at\n // 0, so every frame would write a transform nobody sees), and not while\n // the host is off screen.\n effect(onCleanup => {\n if (!this.ready() || !this.onScreen()) return;\n if (this.platform.prefersReducedMotion()) {\n this.trackEl().nativeElement.style.transform = '';\n return;\n }\n // The static strip is a scroller, and an `overflow: hidden` host keeps\n // whatever offset it was left at: the loop would start shifted, with the\n // edge fades (placed inside the scrolled box) sitting mid-strip.\n this.host.nativeElement.scrollLeft = 0;\n onCleanup(this.startLoop());\n });\n\n // Asked of the rendered copy rather than of `items`: a template node can\n // hold a button of its own, and only the DOM knows.\n afterRenderEffect(() => {\n this.items();\n this.isStatic();\n const first = this.trackEl().nativeElement.querySelector('.wr-marquee__list');\n this.hasFocusableItem.set(first?.querySelector(FOCUSABLE) != null);\n });\n\n // Re-measure on input changes that affect layout (items, gap, height).\n effect(() => {\n this.items();\n this.gap();\n this.itemHeight();\n queueMicrotask(() => this.measure());\n });\n }\n\n protected onEnter(): void {\n if (this.effectiveHoverSpeed() !== undefined) this.isHovered.set(true);\n }\n protected onLeave(): void {\n if (this.effectiveHoverSpeed() !== undefined) this.isHovered.set(false);\n }\n protected isNodeItem = isNode;\n\n // Internals\n\n /** Read first sequence width + decide how many copies cover the viewport. */\n private measure(): void {\n const hostEl = this.host.nativeElement;\n const seqEl = hostEl.querySelector<HTMLElement>('.wr-marquee__list');\n if (!seqEl) return;\n const seqWidth = Math.ceil(seqEl.getBoundingClientRect().width);\n if (seqWidth <= 0) return;\n this.seqWidth.set(seqWidth);\n const containerWidth = hostEl.clientWidth || seqWidth;\n const copies = Math.max(MIN_COPIES, Math.ceil(containerWidth / seqWidth) + COPY_HEADROOM);\n this.copyCount.set(copies);\n }\n\n private watchResize(): void {\n const hostEl = this.host.nativeElement;\n if (typeof ResizeObserver === 'undefined') {\n const onResize = (): void => this.measure();\n window.addEventListener('resize', onResize);\n this.destroyRef.onDestroy(() => window.removeEventListener('resize', onResize));\n return;\n }\n const ro = new ResizeObserver(() => this.measure());\n ro.observe(hostEl);\n const seqEl = hostEl.querySelector<HTMLElement>('.wr-marquee__list');\n if (seqEl) ro.observe(seqEl);\n this.destroyRef.onDestroy(() => ro.disconnect());\n }\n\n private watchImageLoads(): void {\n const hostEl = this.host.nativeElement;\n const seqEl = hostEl.querySelector<HTMLElement>('.wr-marquee__list');\n if (!seqEl) return;\n const imgs = seqEl.querySelectorAll('img');\n if (imgs.length === 0) return;\n let remaining = imgs.length;\n const onDone = (): void => {\n remaining -= 1;\n if (remaining === 0) this.measure();\n };\n imgs.forEach(img => {\n if (img.complete) onDone();\n else {\n img.addEventListener('load', onDone, { once: true });\n img.addEventListener('error', onDone, { once: true });\n }\n });\n }\n\n private watchVisibility(): void {\n // Without an observer (jsdom) the strip simply counts as visible.\n if (typeof IntersectionObserver === 'undefined') return;\n const io = new IntersectionObserver(entries => this.onScreen.set(entries[entries.length - 1].isIntersecting));\n io.observe(this.host.nativeElement);\n this.destroyRef.onDestroy(() => io.disconnect());\n }\n\n /** Start the frame loop; returns what stops it. */\n private startLoop(): () => void {\n const track = this.trackEl().nativeElement;\n let raf = 0;\n let lastTs: number | null = null;\n\n const tick = (ts: number): void => {\n lastTs ??= ts;\n const dt = Math.max(0, ts - lastTs) / 1000;\n lastTs = ts;\n\n const target = this.isHovered() ? (this.effectiveHoverSpeed() ?? this.targetVelocity()) : this.targetVelocity();\n const ease = 1 - Math.exp(-dt / SMOOTH_TAU);\n this.velocity += (target - this.velocity) * ease;\n\n const w = this.seqWidth();\n if (w > 0) {\n this.offset = (((this.offset + this.velocity * dt) % w) + w) % w;\n track.style.transform = `translate3d(${-this.offset}px, 0, 0)`;\n }\n raf = requestAnimationFrame(tick);\n };\n\n raf = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(raf);\n }\n}\n\nexport type { WrMarqueeImage, WrMarqueeNode, WrMarqueeItem } from './types';\n","<div class=\"wr-marquee__track\" #track (mouseenter)=\"onEnter()\" (mouseleave)=\"onLeave()\">\n @for (copyIndex of copyIndices(); track copyIndex) {\n <!--\n `inert` alongside `aria-hidden`, and never one without the other: the copies\n exist only to fill the track, and an item with an `href` renders a real anchor\n in every one of them. Hidden from the screen reader but still in the tab order,\n each duplicate is a tab stop that announces nothing — axe's `aria-hidden-focus`,\n and N stops on one destination for anyone using the keyboard.\n -->\n <ul\n class=\"wr-marquee__list\"\n role=\"list\"\n [attr.aria-hidden]=\"copyIndex > 0\"\n [attr.inert]=\"copyIndex > 0 ? '' : null\"\n [attr.data-copy]=\"copyIndex\"\n >\n @for (item of items(); track $index) {\n <li class=\"wr-marquee__item\" role=\"listitem\">\n @if (item.href) {\n <a\n class=\"wr-marquee__link\"\n [href]=\"item.href\"\n [attr.aria-label]=\"\n (isNodeItem(item) ? item.ariaLabel || item.title : item.alt || item.title) || resolvedLinkLabel()\n \"\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n >\n <ng-container *ngTemplateOutlet=\"content; context: { $implicit: item }\" />\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"content; context: { $implicit: item }\" />\n }\n </li>\n }\n </ul>\n }\n</div>\n\n<ng-template #content let-item>\n @if (isNodeItem(item)) {\n <span\n class=\"wr-marquee__node\"\n [attr.title]=\"item.title || null\"\n [attr.aria-hidden]=\"!!item.href && !item.ariaLabel\"\n >\n <ng-container *ngTemplateOutlet=\"item.node\" />\n </span>\n } @else {\n <img\n [src]=\"item.src\"\n [srcset]=\"item.srcSet || null\"\n [sizes]=\"item.sizes || null\"\n [width]=\"item.width || null\"\n [height]=\"item.height || null\"\n [alt]=\"item.alt || ''\"\n [title]=\"item.title || null\"\n loading=\"lazy\"\n decoding=\"async\"\n draggable=\"false\"\n />\n }\n</ng-template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-bits-marquee.mjs","names":[],"sources":["../../../projects/lib/bits/marquee/marquee.ts","../../../projects/lib/bits/marquee/marquee.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 LogoLoop effect by David Haz / reactbits.dev.\n * Original: https://www.reactbits.dev/animations/logo-loop\n *\n * Renamed `wr-marquee` in ngwr — same rAF-driven infinite carousel,\n * supports image + template items, optional fade/scale/pause-on-hover.\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport { NgTemplateOutlet, isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n ElementRef,\n PLATFORM_ID,\n ViewEncapsulation,\n afterNextRender,\n afterRenderEffect,\n computed,\n effect,\n inject,\n input,\n signal,\n viewChild,\n} from '@angular/core';\n\nimport { useI18nText } from 'ngwr/i18n';\nimport { WrPlatform } from 'ngwr/platform';\nimport { numAttr } from 'ngwr/utils';\n\nimport type { WrMarqueeNode, WrMarqueeItem } from './types';\n\nconst SMOOTH_TAU = 0.25;\nconst MIN_COPIES = 2;\nconst COPY_HEADROOM = 2;\n\nfunction isNode(item: WrMarqueeItem): item is WrMarqueeNode {\n return 'node' in item;\n}\n\n/**\n * What takes a tab stop of its own inside an item. Not `getFocusableElements()`,\n * which drops anything without a layout box: whether the strip already has a\n * stop is a question about its markup, and it is asked of the one copy left.\n */\nconst FOCUSABLE =\n 'a[href], area[href], button:not([disabled]), input:not([disabled]):not([type=\"hidden\"]), ' +\n 'select:not([disabled]), textarea:not([disabled]), iframe, [contenteditable]:not([contenteditable=\"false\"]), ' +\n '[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Endless horizontal marquee. Auto-duplicates the items list until it\n * covers the container width, then animates the track with a smooth\n * rAF-driven scroll. Optional edge fades, scale-on-hover, and pause /\n * variable-speed hover behaviour.\n *\n * Under `prefers-reduced-motion` the strip stops moving and becomes a plain\n * horizontal scroller instead: one copy of the items, no edge fades, and a tab\n * stop on the region itself when no item takes one, so every item stays in\n * reach of a pointer and of the keyboard.\n *\n * @example\n * ```html\n * <wr-marquee\n * [items]=\"logos\"\n * [speed]=\"80\"\n * [gap]=\"48\"\n * [itemHeight]=\"36\"\n * [fadeOut]=\"true\"\n * [scaleOnHover]=\"true\"\n * [pauseOnHover]=\"true\"\n * />\n * ```\n *\n * @see https://www.reactbits.dev/animations/logo-loop\n */\n@Component({\n selector: 'wr-marquee',\n templateUrl: './marquee.html',\n styleUrl: './marquee.scss',\n encapsulation: ViewEncapsulation.None,\n imports: [NgTemplateOutlet],\n host: {\n class: 'wr-marquee',\n '[class.wr-marquee--fade]': 'fadeOut()',\n '[class.wr-marquee--scale-hover]': 'scaleOnHover()',\n '[style.--wr-marquee-gap.px]': 'gap()',\n '[style.--wr-marquee-height.px]': 'itemHeight()',\n '[style.--wr-marquee-input-fade-color]': 'fadeOutColor() || null',\n '[class.wr-marquee--static]': 'isStatic()',\n '[attr.role]': '\"region\"',\n '[attr.aria-label]': 'resolvedAriaLabel()',\n '[attr.tabindex]': 'scrollerTabIndex()',\n },\n})\nexport class WrMarquee {\n /** Items to display. Each entry is either an image source or a TemplateRef node. */\n readonly items = input.required<readonly WrMarqueeItem[]>();\n\n /** Scroll speed in pixels per second. Negative reverses direction. @default 120 */\n readonly speed = input(120, { transform: numAttr(120) });\n\n /** Track direction. @default 'left' */\n readonly direction = input<'left' | 'right'>('left');\n\n /** Item height in pixels. @default 28 */\n readonly itemHeight = input(28, { transform: numAttr(28) });\n\n /** Gap between items in pixels. @default 32 */\n readonly gap = input(32, { transform: numAttr(32) });\n\n /** Pause the loop on hover. @default false */\n readonly pauseOnHover = input(false, { transform: coerceBooleanProperty });\n\n /** Custom speed (px/s) when hovered. Overrides `pauseOnHover`. */\n readonly hoverSpeed = input<number | undefined>(undefined, {\n transform: (v: unknown): number | undefined => (v == null || v === '' ? undefined : coerceNumberProperty(v, 0)),\n });\n\n /** Apply edge fade-out gradients. @default false */\n readonly fadeOut = input(false, { transform: coerceBooleanProperty });\n\n /** CSS colour for the fade-out gradient. Defaults to the page background. */\n readonly fadeOutColor = input<string>('');\n\n /** Scale individual items up on hover. @default false */\n readonly scaleOnHover = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Accessible name for the region. Falls back to `marquee.label`, then\n * `'Marquee'`.\n */\n readonly ariaLabel = input<string | null>(null);\n\n protected readonly resolvedAriaLabel = useI18nText(this.ariaLabel, 'marquee.label', 'Marquee');\n\n /**\n * Name used for an item that links somewhere but carries no text of its own.\n * Falls back to `marquee.link`, then `'link'` — a bare `'link'` in English was\n * what a screen reader announced for every unlabelled logo in the strip.\n */\n protected readonly resolvedLinkLabel = useI18nText(signal(null).asReadonly(), 'marquee.link', 'link');\n\n private readonly trackEl = viewChild.required<ElementRef<HTMLElement>>('track');\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly destroyRef = inject(DestroyRef);\n private readonly platform = inject(WrPlatform);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n /** Flips once the first render has been measured — the loop waits for it. */\n private readonly ready = signal(false);\n /** Whether the host is in the viewport; stays `true` without an observer. */\n private readonly onScreen = signal(true);\n /** Loop state, kept across a park so the strip resumes where it stopped. */\n private offset = 0;\n private velocity = 0;\n\n protected readonly seqWidth = signal(0);\n protected readonly copyCount = signal(MIN_COPIES);\n protected readonly isHovered = signal(false);\n\n /**\n * Reduced motion, read live. The loop stops, and a frozen strip would leave\n * every item past the edge out of reach — so the host scrolls instead\n * (`.wr-marquee--static`), and the duplicate copies, which exist only to hide\n * the seam of a loop, are not rendered.\n */\n protected readonly isStatic = this.platform.prefersReducedMotion;\n\n protected readonly copyIndices = computed(() =>\n this.isStatic() ? [0] : Array.from({ length: this.copyCount() }, (_, i) => i)\n );\n\n /** Whether an item already takes a tab stop, which the keyboard can scroll the strip to. */\n private readonly hasFocusableItem = signal(false);\n\n /**\n * The static strip is a scroll container, and one the keyboard cannot reach\n * cannot be scrolled from it. A focused link scrolls itself into view, so the\n * region takes a stop of its own only when no item has one. It carries the\n * region's name.\n */\n protected readonly scrollerTabIndex = computed(() => (this.isStatic() && !this.hasFocusableItem() ? 0 : null));\n\n /** Resolved hover speed: `hoverSpeed` overrides, else 0 when `pauseOnHover`, else live. */\n private readonly effectiveHoverSpeed = computed<number | undefined>(() => {\n const explicit = this.hoverSpeed();\n if (explicit !== undefined) return explicit;\n return this.pauseOnHover() ? 0 : undefined;\n });\n\n /** Target velocity in px/s, sign baked in. */\n private readonly targetVelocity = computed(() => {\n const s = this.speed();\n const sign = this.direction() === 'left' ? 1 : -1;\n return Math.abs(s) * sign * (s < 0 ? -1 : 1);\n });\n\n constructor() {\n if (!this.isBrowser) return;\n\n afterNextRender(() => {\n this.measure();\n this.watchResize();\n this.watchImageLoads();\n this.watchVisibility();\n this.ready.set(true);\n });\n\n // The loop runs only while there is something to see: never under\n // `prefers-reduced-motion` (read live — the stylesheet pins the track at\n // 0, so every frame would write a transform nobody sees), and not while\n // the host is off screen.\n effect(onCleanup => {\n if (!this.ready() || !this.onScreen()) return;\n if (this.platform.prefersReducedMotion()) {\n this.trackEl().nativeElement.style.transform = '';\n return;\n }\n // The static strip is a scroller, and an `overflow: hidden` host keeps\n // whatever offset it was left at: the loop would start shifted, with the\n // edge fades (placed inside the scrolled box) sitting mid-strip.\n this.host.nativeElement.scrollLeft = 0;\n onCleanup(this.startLoop());\n });\n\n // Asked of the rendered copy rather than of `items`: a template node can\n // hold a button of its own, and only the DOM knows.\n afterRenderEffect(() => {\n this.items();\n this.isStatic();\n const first = this.trackEl().nativeElement.querySelector('.wr-marquee__list');\n this.hasFocusableItem.set(first?.querySelector(FOCUSABLE) != null);\n });\n\n // Re-measure on input changes that affect layout (items, gap, height).\n effect(() => {\n this.items();\n this.gap();\n this.itemHeight();\n queueMicrotask(() => this.measure());\n });\n }\n\n protected onEnter(): void {\n if (this.effectiveHoverSpeed() !== undefined) this.isHovered.set(true);\n }\n protected onLeave(): void {\n if (this.effectiveHoverSpeed() !== undefined) this.isHovered.set(false);\n }\n protected isNodeItem = isNode;\n\n // Internals\n\n /** Read first sequence width + decide how many copies cover the viewport. */\n private measure(): void {\n const hostEl = this.host.nativeElement;\n const seqEl = hostEl.querySelector<HTMLElement>('.wr-marquee__list');\n if (!seqEl) return;\n const seqWidth = Math.ceil(seqEl.getBoundingClientRect().width);\n if (seqWidth <= 0) return;\n this.seqWidth.set(seqWidth);\n const containerWidth = hostEl.clientWidth || seqWidth;\n const copies = Math.max(MIN_COPIES, Math.ceil(containerWidth / seqWidth) + COPY_HEADROOM);\n this.copyCount.set(copies);\n }\n\n private watchResize(): void {\n const hostEl = this.host.nativeElement;\n if (typeof ResizeObserver === 'undefined') {\n const onResize = (): void => this.measure();\n window.addEventListener('resize', onResize);\n this.destroyRef.onDestroy(() => window.removeEventListener('resize', onResize));\n return;\n }\n const ro = new ResizeObserver(() => this.measure());\n ro.observe(hostEl);\n const seqEl = hostEl.querySelector<HTMLElement>('.wr-marquee__list');\n if (seqEl) ro.observe(seqEl);\n this.destroyRef.onDestroy(() => ro.disconnect());\n }\n\n private watchImageLoads(): void {\n const hostEl = this.host.nativeElement;\n const seqEl = hostEl.querySelector<HTMLElement>('.wr-marquee__list');\n if (!seqEl) return;\n const imgs = seqEl.querySelectorAll('img');\n if (imgs.length === 0) return;\n let remaining = imgs.length;\n const onDone = (): void => {\n remaining -= 1;\n if (remaining === 0) this.measure();\n };\n imgs.forEach(img => {\n if (img.complete) onDone();\n else {\n img.addEventListener('load', onDone, { once: true });\n img.addEventListener('error', onDone, { once: true });\n }\n });\n }\n\n private watchVisibility(): void {\n // Without an observer (jsdom) the strip simply counts as visible.\n if (typeof IntersectionObserver === 'undefined') return;\n const io = new IntersectionObserver(entries => this.onScreen.set(entries[entries.length - 1].isIntersecting));\n io.observe(this.host.nativeElement);\n this.destroyRef.onDestroy(() => io.disconnect());\n }\n\n /** Start the frame loop; returns what stops it. */\n private startLoop(): () => void {\n const track = this.trackEl().nativeElement;\n let raf = 0;\n let lastTs: number | null = null;\n\n const tick = (ts: number): void => {\n lastTs ??= ts;\n const dt = Math.max(0, ts - lastTs) / 1000;\n lastTs = ts;\n\n const target = this.isHovered() ? (this.effectiveHoverSpeed() ?? this.targetVelocity()) : this.targetVelocity();\n const ease = 1 - Math.exp(-dt / SMOOTH_TAU);\n this.velocity += (target - this.velocity) * ease;\n\n const w = this.seqWidth();\n if (w > 0) {\n this.offset = (((this.offset + this.velocity * dt) % w) + w) % w;\n track.style.transform = `translate3d(${-this.offset}px, 0, 0)`;\n }\n raf = requestAnimationFrame(tick);\n };\n\n raf = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(raf);\n }\n}\n\nexport type { WrMarqueeImage, WrMarqueeNode, WrMarqueeItem } from './types';\n","<div class=\"wr-marquee__track\" #track (mouseenter)=\"onEnter()\" (mouseleave)=\"onLeave()\">\n @for (copyIndex of copyIndices(); track copyIndex) {\n <!--\n `inert` alongside `aria-hidden`, and never one without the other: the copies\n exist only to fill the track, and an item with an `href` renders a real anchor\n in every one of them. Hidden from the screen reader but still in the tab order,\n each duplicate is a tab stop that announces nothing — axe's `aria-hidden-focus`,\n and N stops on one destination for anyone using the keyboard.\n -->\n <ul\n class=\"wr-marquee__list\"\n role=\"list\"\n [attr.aria-hidden]=\"copyIndex > 0\"\n [attr.inert]=\"copyIndex > 0 ? '' : null\"\n [attr.data-copy]=\"copyIndex\"\n >\n @for (item of items(); track $index) {\n <li class=\"wr-marquee__item\" role=\"listitem\">\n @if (item.href) {\n <a\n class=\"wr-marquee__link\"\n [href]=\"item.href\"\n [attr.aria-label]=\"\n (isNodeItem(item) ? item.ariaLabel || item.title : item.alt || item.title) || resolvedLinkLabel()\n \"\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n >\n <ng-container *ngTemplateOutlet=\"content; context: { $implicit: item }\" />\n </a>\n } @else {\n <ng-container *ngTemplateOutlet=\"content; context: { $implicit: item }\" />\n }\n </li>\n }\n </ul>\n }\n</div>\n\n<ng-template #content let-item>\n @if (isNodeItem(item)) {\n <span\n class=\"wr-marquee__node\"\n [attr.title]=\"item.title || null\"\n [attr.aria-hidden]=\"!!item.href && !item.ariaLabel\"\n >\n <ng-container *ngTemplateOutlet=\"item.node\" />\n </span>\n } @else {\n <img\n [src]=\"item.src\"\n [srcset]=\"item.srcSet || null\"\n [sizes]=\"item.sizes || null\"\n [width]=\"item.width || null\"\n [height]=\"item.height || null\"\n [alt]=\"item.alt || ''\"\n [title]=\"item.title || null\"\n loading=\"lazy\"\n decoding=\"async\"\n draggable=\"false\"\n />\n }\n</ng-template>\n"],"mappings":"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"}
|
|
@@ -111,10 +111,7 @@ var WrRippleGrid = class WrRippleGrid {
|
|
|
111
111
|
});
|
|
112
112
|
cell = computed(() => Math.max(MIN_CELL, this.cellSize()), ...ngDevMode ? [{ debugName: "cell" }] : /* istanbul ignore next */ []);
|
|
113
113
|
stroke = computed(() => Math.max(MIN_LINE_WIDTH, this.lineWidth()), ...ngDevMode ? [{ debugName: "stroke" }] : /* istanbul ignore next */ []);
|
|
114
|
-
standInColor = computed(() => {
|
|
115
|
-
const color = this.lineColor()?.trim() ?? "";
|
|
116
|
-
return color === "" || color.includes("--wr-ripple-grid-line-color") ? null : color;
|
|
117
|
-
}, ...ngDevMode ? [{ debugName: "standInColor" }] : /* istanbul ignore next */ []);
|
|
114
|
+
standInColor = computed(() => (this.lineColor() ?? "").trim() || null, ...ngDevMode ? [{ debugName: "standInColor" }] : /* istanbul ignore next */ []);
|
|
118
115
|
lineExpression = computed(() => (this.lineColor() ?? "").trim() || LINE_TOKEN, ...ngDevMode ? [{ debugName: "lineExpression" }] : /* istanbul ignore next */ []);
|
|
119
116
|
rippleExpression = computed(() => (this.rippleColor() ?? "").trim() || RIPPLE_TOKEN, ...ngDevMode ? [{ debugName: "rippleExpression" }] : /* istanbul ignore next */ []);
|
|
120
117
|
idle = computed(() => {
|
|
@@ -602,7 +599,7 @@ var WrRippleGrid = class WrRippleGrid {
|
|
|
602
599
|
}
|
|
603
600
|
static ɵfac = i0.ɵɵngDeclareFactory({
|
|
604
601
|
minVersion: "12.0.0",
|
|
605
|
-
version: "22.2.
|
|
602
|
+
version: "22.2.4",
|
|
606
603
|
ngImport: i0,
|
|
607
604
|
type: WrRippleGrid,
|
|
608
605
|
deps: [],
|
|
@@ -610,7 +607,7 @@ var WrRippleGrid = class WrRippleGrid {
|
|
|
610
607
|
});
|
|
611
608
|
static ɵcmp = i0.ɵɵngDeclareComponent({
|
|
612
609
|
minVersion: "17.2.0",
|
|
613
|
-
version: "22.2.
|
|
610
|
+
version: "22.2.4",
|
|
614
611
|
type: WrRippleGrid,
|
|
615
612
|
isStandalone: true,
|
|
616
613
|
selector: "wr-ripple-grid",
|
|
@@ -683,7 +680,7 @@ var WrRippleGrid = class WrRippleGrid {
|
|
|
683
680
|
properties: {
|
|
684
681
|
"style.--wr-ripple-grid-cell-size.px": "cell()",
|
|
685
682
|
"style.--wr-ripple-grid-line-width.px": "stroke()",
|
|
686
|
-
"style.--wr-ripple-grid-line-color": "standInColor()",
|
|
683
|
+
"style.--wr-ripple-grid-input-line-color": "standInColor()",
|
|
687
684
|
"class.wr-ripple-grid--painted": "painted()"
|
|
688
685
|
},
|
|
689
686
|
classAttribute: "wr-ripple-grid"
|
|
@@ -698,13 +695,13 @@ var WrRippleGrid = class WrRippleGrid {
|
|
|
698
695
|
ngImport: i0,
|
|
699
696
|
template: "<canvas #canvas class=\"wr-ripple-grid__canvas\" aria-hidden=\"true\"></canvas>",
|
|
700
697
|
isInline: true,
|
|
701
|
-
styles: [".wr-ripple-grid{--wr-ripple-grid-line-color: var(--wr-border-base);--wr-ripple-grid-ripple-color: var(--wr-color-primary);--wr-ripple-grid-cell-size: 40px;--wr-ripple-grid-line-width: 1px;position:absolute;inset:0;z-index:0;display:block;overflow:hidden;pointer-events:none;background-image:linear-gradient(to right,var(--wr-ripple-grid-line-color) var(--wr-ripple-grid-line-width),transparent var(--wr-ripple-grid-line-width)),linear-gradient(to bottom,var(--wr-ripple-grid-line-color) var(--wr-ripple-grid-line-width),transparent var(--wr-ripple-grid-line-width));background-position:calc(50% - var(--wr-ripple-grid-line-width) / 2) calc(50% - var(--wr-ripple-grid-line-width) / 2);background-size:var(--wr-ripple-grid-cell-size) var(--wr-ripple-grid-cell-size)}.wr-ripple-grid--painted{background-image:none}.wr-ripple-grid__canvas{display:block;width:100%;height:100%}\n"],
|
|
698
|
+
styles: [".wr-ripple-grid{--wr-ripple-grid-line-color: var(--wr-border-base);--wr-ripple-grid-ripple-color: var(--wr-color-primary);--wr-ripple-grid-cell-size: 40px;--wr-ripple-grid-line-width: 1px;position:absolute;inset:0;z-index:0;display:block;overflow:hidden;pointer-events:none;background-image:linear-gradient(to right,var(--wr-ripple-grid-input-line-color, var(--wr-ripple-grid-line-color)) var(--wr-ripple-grid-line-width),transparent var(--wr-ripple-grid-line-width)),linear-gradient(to bottom,var(--wr-ripple-grid-input-line-color, var(--wr-ripple-grid-line-color)) var(--wr-ripple-grid-line-width),transparent var(--wr-ripple-grid-line-width));background-position:calc(50% - var(--wr-ripple-grid-line-width) / 2) calc(50% - var(--wr-ripple-grid-line-width) / 2);background-size:var(--wr-ripple-grid-cell-size) var(--wr-ripple-grid-cell-size)}.wr-ripple-grid--painted{background-image:none}.wr-ripple-grid__canvas{display:block;width:100%;height:100%}@media(forced-colors:active){.wr-ripple-grid__canvas{forced-color-adjust:none}}\n"],
|
|
702
699
|
encapsulation: i0.ViewEncapsulation.None
|
|
703
700
|
});
|
|
704
701
|
};
|
|
705
702
|
i0.ɵɵngDeclareClassMetadata({
|
|
706
703
|
minVersion: "12.0.0",
|
|
707
|
-
version: "22.2.
|
|
704
|
+
version: "22.2.4",
|
|
708
705
|
ngImport: i0,
|
|
709
706
|
type: WrRippleGrid,
|
|
710
707
|
decorators: [{
|
|
@@ -717,10 +714,10 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
717
714
|
class: "wr-ripple-grid",
|
|
718
715
|
"[style.--wr-ripple-grid-cell-size.px]": "cell()",
|
|
719
716
|
"[style.--wr-ripple-grid-line-width.px]": "stroke()",
|
|
720
|
-
"[style.--wr-ripple-grid-line-color]": "standInColor()",
|
|
717
|
+
"[style.--wr-ripple-grid-input-line-color]": "standInColor()",
|
|
721
718
|
"[class.wr-ripple-grid--painted]": "painted()"
|
|
722
719
|
},
|
|
723
|
-
styles: [".wr-ripple-grid{--wr-ripple-grid-line-color: var(--wr-border-base);--wr-ripple-grid-ripple-color: var(--wr-color-primary);--wr-ripple-grid-cell-size: 40px;--wr-ripple-grid-line-width: 1px;position:absolute;inset:0;z-index:0;display:block;overflow:hidden;pointer-events:none;background-image:linear-gradient(to right,var(--wr-ripple-grid-line-color) var(--wr-ripple-grid-line-width),transparent var(--wr-ripple-grid-line-width)),linear-gradient(to bottom,var(--wr-ripple-grid-line-color) var(--wr-ripple-grid-line-width),transparent var(--wr-ripple-grid-line-width));background-position:calc(50% - var(--wr-ripple-grid-line-width) / 2) calc(50% - var(--wr-ripple-grid-line-width) / 2);background-size:var(--wr-ripple-grid-cell-size) var(--wr-ripple-grid-cell-size)}.wr-ripple-grid--painted{background-image:none}.wr-ripple-grid__canvas{display:block;width:100%;height:100%}\n"]
|
|
720
|
+
styles: [".wr-ripple-grid{--wr-ripple-grid-line-color: var(--wr-border-base);--wr-ripple-grid-ripple-color: var(--wr-color-primary);--wr-ripple-grid-cell-size: 40px;--wr-ripple-grid-line-width: 1px;position:absolute;inset:0;z-index:0;display:block;overflow:hidden;pointer-events:none;background-image:linear-gradient(to right,var(--wr-ripple-grid-input-line-color, var(--wr-ripple-grid-line-color)) var(--wr-ripple-grid-line-width),transparent var(--wr-ripple-grid-line-width)),linear-gradient(to bottom,var(--wr-ripple-grid-input-line-color, var(--wr-ripple-grid-line-color)) var(--wr-ripple-grid-line-width),transparent var(--wr-ripple-grid-line-width));background-position:calc(50% - var(--wr-ripple-grid-line-width) / 2) calc(50% - var(--wr-ripple-grid-line-width) / 2);background-size:var(--wr-ripple-grid-cell-size) var(--wr-ripple-grid-cell-size)}.wr-ripple-grid--painted{background-image:none}.wr-ripple-grid__canvas{display:block;width:100%;height:100%}@media(forced-colors:active){.wr-ripple-grid__canvas{forced-color-adjust:none}}\n"]
|
|
724
721
|
}]
|
|
725
722
|
}],
|
|
726
723
|
ctorParameters: () => [],
|