ngwr 11.1.0 → 11.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/circular-text/styles/_index.scss +11 -0
- package/command-palette/styles/_index.scss +12 -0
- package/fesm2022/ngwr-action-sheet-testing.mjs +313 -0
- package/fesm2022/ngwr-action-sheet-testing.mjs.map +1 -0
- package/fesm2022/ngwr-bar-chart-testing.mjs +89 -0
- package/fesm2022/ngwr-bar-chart-testing.mjs.map +1 -0
- package/fesm2022/ngwr-blur-text-testing.mjs +203 -0
- package/fesm2022/ngwr-blur-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-blur-text.mjs +15 -1
- package/fesm2022/ngwr-blur-text.mjs.map +1 -1
- package/fesm2022/ngwr-border-glow-testing.mjs +325 -0
- package/fesm2022/ngwr-border-glow-testing.mjs.map +1 -0
- package/fesm2022/ngwr-calendar-heatmap-testing.mjs +95 -0
- package/fesm2022/ngwr-calendar-heatmap-testing.mjs.map +1 -0
- package/fesm2022/ngwr-calendar-testing.mjs +368 -0
- package/fesm2022/ngwr-calendar-testing.mjs.map +1 -0
- package/fesm2022/ngwr-calendar.mjs +43 -3
- package/fesm2022/ngwr-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-cascader.mjs +12 -0
- package/fesm2022/ngwr-cascader.mjs.map +1 -1
- package/fesm2022/ngwr-checkbox.mjs +8 -0
- package/fesm2022/ngwr-checkbox.mjs.map +1 -1
- package/fesm2022/ngwr-circular-text-testing.mjs +230 -0
- package/fesm2022/ngwr-circular-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-circular-text.mjs +2 -2
- package/fesm2022/ngwr-circular-text.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker-testing.mjs +432 -0
- package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -0
- package/fesm2022/ngwr-color-picker.mjs +29 -3
- package/fesm2022/ngwr-color-picker.mjs.map +1 -1
- package/fesm2022/ngwr-context-menu.mjs +25 -5
- package/fesm2022/ngwr-context-menu.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker-testing.mjs +10 -79
- package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-decrypt-text-testing.mjs +261 -0
- package/fesm2022/ngwr-decrypt-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-decrypt-text.mjs +6 -1
- package/fesm2022/ngwr-decrypt-text.mjs.map +1 -1
- package/fesm2022/ngwr-donut-chart-testing.mjs +93 -0
- package/fesm2022/ngwr-donut-chart-testing.mjs.map +1 -0
- package/fesm2022/ngwr-drawer.mjs +36 -7
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown.mjs +32 -2
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar-testing.mjs +446 -0
- package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -0
- package/fesm2022/ngwr-event-calendar.mjs +43 -18
- package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-falling-text-testing.mjs +218 -0
- package/fesm2022/ngwr-falling-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-fuzzy-text-testing.mjs +134 -0
- package/fesm2022/ngwr-fuzzy-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-gauge-testing.mjs +86 -0
- package/fesm2022/ngwr-gauge-testing.mjs.map +1 -0
- package/fesm2022/ngwr-glitch-text-testing.mjs +181 -0
- package/fesm2022/ngwr-glitch-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-gradient-text-testing.mjs +223 -0
- package/fesm2022/ngwr-gradient-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-i18n-en.mjs +14 -0
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +14 -0
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-image-cropper-testing.mjs +156 -0
- package/fesm2022/ngwr-image-cropper-testing.mjs.map +1 -0
- package/fesm2022/ngwr-image-cropper.mjs +10 -3
- package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +73 -6
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-knob-testing.mjs +220 -0
- package/fesm2022/ngwr-knob-testing.mjs.map +1 -0
- package/fesm2022/ngwr-lightbox-testing.mjs +223 -0
- package/fesm2022/ngwr-lightbox-testing.mjs.map +1 -0
- package/fesm2022/ngwr-lightbox.mjs +9 -2
- package/fesm2022/ngwr-lightbox.mjs.map +1 -1
- package/fesm2022/ngwr-line-chart-testing.mjs +119 -0
- package/fesm2022/ngwr-line-chart-testing.mjs.map +1 -0
- package/fesm2022/ngwr-marquee-testing.mjs +336 -0
- package/fesm2022/ngwr-marquee-testing.mjs.map +1 -0
- package/fesm2022/ngwr-meter-group-testing.mjs +80 -0
- package/fesm2022/ngwr-meter-group-testing.mjs.map +1 -0
- package/fesm2022/ngwr-overlay.mjs +28 -1
- package/fesm2022/ngwr-overlay.mjs.map +1 -1
- package/fesm2022/ngwr-popover.mjs +14 -0
- package/fesm2022/ngwr-popover.mjs.map +1 -1
- package/fesm2022/ngwr-rotating-text-testing.mjs +220 -0
- package/fesm2022/ngwr-rotating-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-rotating-text.mjs +2 -2
- package/fesm2022/ngwr-rotating-text.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +70 -11
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text-testing.mjs +237 -0
- package/fesm2022/ngwr-shiny-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-slider.mjs +44 -6
- package/fesm2022/ngwr-slider.mjs.map +1 -1
- package/fesm2022/ngwr-sparkline-testing.mjs +76 -0
- package/fesm2022/ngwr-sparkline-testing.mjs.map +1 -0
- package/fesm2022/ngwr-speed-dial-testing.mjs +279 -0
- package/fesm2022/ngwr-speed-dial-testing.mjs.map +1 -0
- package/fesm2022/ngwr-splash-cursor-testing.mjs +89 -0
- package/fesm2022/ngwr-splash-cursor-testing.mjs.map +1 -0
- package/fesm2022/ngwr-split-text-testing.mjs +224 -0
- package/fesm2022/ngwr-split-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-split-text.mjs +15 -1
- package/fesm2022/ngwr-split-text.mjs.map +1 -1
- package/fesm2022/ngwr-splitter-testing.mjs +245 -0
- package/fesm2022/ngwr-splitter-testing.mjs.map +1 -0
- package/fesm2022/ngwr-spotlight-card-testing.mjs +368 -0
- package/fesm2022/ngwr-spotlight-card-testing.mjs.map +1 -0
- package/fesm2022/ngwr-star-border-testing.mjs +172 -0
- package/fesm2022/ngwr-star-border-testing.mjs.map +1 -0
- package/fesm2022/ngwr-table.mjs +13 -3
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-tabs.mjs +15 -1
- package/fesm2022/ngwr-tabs.mjs.map +1 -1
- package/fesm2022/ngwr-tilt-card-testing.mjs +190 -0
- package/fesm2022/ngwr-tilt-card-testing.mjs.map +1 -0
- package/fesm2022/ngwr-tilt-card.mjs +19 -1
- package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
- package/fesm2022/ngwr-toast.mjs +36 -7
- package/fesm2022/ngwr-toast.mjs.map +1 -1
- package/fesm2022/ngwr-tour-testing.mjs +173 -0
- package/fesm2022/ngwr-tour-testing.mjs.map +1 -0
- package/fesm2022/ngwr-tour.mjs +2 -2
- package/fesm2022/ngwr-tour.mjs.map +1 -1
- package/fesm2022/ngwr-tree.mjs +10 -3
- package/fesm2022/ngwr-tree.mjs.map +1 -1
- package/fesm2022/ngwr-typewriter-testing.mjs +228 -0
- package/fesm2022/ngwr-typewriter-testing.mjs.map +1 -0
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/fesm2022/ngwr-waves-testing.mjs +128 -0
- package/fesm2022/ngwr-waves-testing.mjs.map +1 -0
- package/fesm2022/ngwr-window-testing.mjs +361 -0
- package/fesm2022/ngwr-window-testing.mjs.map +1 -0
- package/fesm2022/ngwr-window.mjs +84 -10
- package/fesm2022/ngwr-window.mjs.map +1 -1
- package/i18n/en.json +16 -2
- package/i18n/ru.json +16 -2
- package/llms-full.txt +147 -0
- package/llms.txt +27 -8
- package/mcp/providers.js +29 -0
- package/mcp/tools.js +1 -29
- package/package.json +145 -1
- package/skills/ngwr/SKILL.md +115 -0
- package/skills/ngwr/references/catalog.md +212 -0
- package/skills/ngwr/references/setup.md +73 -0
- package/types/ngwr-action-sheet-testing.d.ts +260 -0
- package/types/ngwr-bar-chart-testing.d.ts +77 -0
- package/types/ngwr-blur-text-testing.d.ts +180 -0
- package/types/ngwr-blur-text.d.ts +12 -0
- package/types/ngwr-border-glow-testing.d.ts +219 -0
- package/types/ngwr-calendar-heatmap-testing.d.ts +79 -0
- package/types/ngwr-calendar-testing.d.ts +277 -0
- package/types/ngwr-calendar.d.ts +10 -0
- package/types/ngwr-checkbox.d.ts +13 -0
- package/types/ngwr-circular-text-testing.d.ts +196 -0
- package/types/ngwr-color-picker-testing.d.ts +291 -0
- package/types/ngwr-color-picker.d.ts +13 -1
- package/types/ngwr-context-menu.d.ts +15 -1
- package/types/ngwr-date-picker-testing.d.ts +6 -66
- package/types/ngwr-decrypt-text-testing.d.ts +222 -0
- package/types/ngwr-donut-chart-testing.d.ts +79 -0
- package/types/ngwr-drawer.d.ts +12 -0
- package/types/ngwr-dropdown.d.ts +9 -0
- package/types/ngwr-event-calendar-testing.d.ts +301 -0
- package/types/ngwr-event-calendar.d.ts +9 -6
- package/types/ngwr-falling-text-testing.d.ts +186 -0
- package/types/ngwr-fuzzy-text-testing.d.ts +130 -0
- package/types/ngwr-gauge-testing.d.ts +71 -0
- package/types/ngwr-glitch-text-testing.d.ts +172 -0
- package/types/ngwr-gradient-text-testing.d.ts +173 -0
- package/types/ngwr-image-cropper-testing.d.ts +134 -0
- package/types/ngwr-image-cropper.d.ts +1 -0
- package/types/ngwr-input-number.d.ts +33 -1
- package/types/ngwr-knob-testing.d.ts +150 -0
- package/types/ngwr-lightbox-testing.d.ts +154 -0
- package/types/ngwr-lightbox.d.ts +6 -0
- package/types/ngwr-line-chart-testing.d.ts +93 -0
- package/types/ngwr-marquee-testing.d.ts +280 -0
- package/types/ngwr-meter-group-testing.d.ts +67 -0
- package/types/ngwr-overlay.d.ts +19 -1
- package/types/ngwr-rotating-text-testing.d.ts +191 -0
- package/types/ngwr-select.d.ts +32 -0
- package/types/ngwr-shiny-text-testing.d.ts +165 -0
- package/types/ngwr-slider.d.ts +15 -0
- package/types/ngwr-sparkline-testing.d.ts +69 -0
- package/types/ngwr-speed-dial-testing.d.ts +195 -0
- package/types/ngwr-splash-cursor-testing.d.ts +95 -0
- package/types/ngwr-split-text-testing.d.ts +209 -0
- package/types/ngwr-split-text.d.ts +12 -0
- package/types/ngwr-splitter-testing.d.ts +174 -0
- package/types/ngwr-spotlight-card-testing.d.ts +303 -0
- package/types/ngwr-star-border-testing.d.ts +135 -0
- package/types/ngwr-table.d.ts +10 -1
- package/types/ngwr-tilt-card-testing.d.ts +158 -0
- package/types/ngwr-tilt-card.d.ts +2 -0
- package/types/ngwr-toast.d.ts +14 -5
- package/types/ngwr-tour-testing.d.ts +143 -0
- package/types/ngwr-tree.d.ts +7 -0
- package/types/ngwr-typewriter-testing.d.ts +201 -0
- package/types/ngwr-version.d.ts +1 -1
- package/types/ngwr-waves-testing.d.ts +121 -0
- package/types/ngwr-window-testing.d.ts +263 -0
- package/types/ngwr-window.d.ts +27 -10
- package/window/styles/_index.scss +44 -16
package/fesm2022/ngwr-tabs.mjs
CHANGED
|
@@ -231,8 +231,22 @@ class WrTabs {
|
|
|
231
231
|
const tabs = this.tabs().filter(t => !t.disabled());
|
|
232
232
|
if (tabs.length === 0)
|
|
233
233
|
return;
|
|
234
|
+
// Step from where FOCUS is, not from what is active.
|
|
235
|
+
//
|
|
236
|
+
// In router mode nothing ever moves `active` — `onTabClick` and this
|
|
237
|
+
// handler both gate `activate()` on `!isRouter()`, because the route is
|
|
238
|
+
// what selects a tab there. Reading the neighbour off `activeTab()`
|
|
239
|
+
// therefore recomputed the same `idx` on every press, and ArrowRight
|
|
240
|
+
// walked from the first tab to the second and stayed there for the rest of
|
|
241
|
+
// the session. In manual mode focus and activation move together, so the
|
|
242
|
+
// two readings agree and nothing changes.
|
|
243
|
+
const strip = this.stripRef()?.nativeElement;
|
|
244
|
+
const focused = strip?.ownerDocument.activeElement;
|
|
245
|
+
const focusedIdx = focused instanceof HTMLElement && strip?.contains(focused)
|
|
246
|
+
? tabs.findIndex(t => this.headerId(t.key()) === focused.id)
|
|
247
|
+
: -1;
|
|
234
248
|
const active = this.activeTab();
|
|
235
|
-
const idx = active ? tabs.indexOf(active) : -1;
|
|
249
|
+
const idx = focusedIdx >= 0 ? focusedIdx : active ? tabs.indexOf(active) : -1;
|
|
236
250
|
// The two neighbours in DOM order, wrapping at the ends.
|
|
237
251
|
const forward = idx < tabs.length - 1 ? idx + 1 : 0;
|
|
238
252
|
const backward = idx > 0 ? idx - 1 : tabs.length - 1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-tabs.mjs","sources":["../../../projects/lib/tabs/tokens/tabs.token.ts","../../../projects/lib/tabs/tab.ts","../../../projects/lib/tabs/tabs.ts","../../../projects/lib/tabs/tabs.html","../../../projects/lib/tabs/ngwr-tabs.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 { InjectionToken, type Signal } from '@angular/core';\n\n/**\n * Contract a `<wr-tab>` uses to talk to its parent `<wr-tabs>`.\n *\n * @internal\n */\nexport interface WrTabsContext {\n /** Currently active tab key (component-managed only). */\n readonly active: Signal<string | null>;\n /** Activate a tab by key (component-managed only). */\n activate(key: string): void;\n /** Register a child tab so the parent can pick a default. */\n register(tab: { key: string; routerLink: unknown }): void;\n /** Unregister on destroy. */\n unregister(key: string): void;\n}\n\n/**\n * Token a `<wr-tab>` injects to register itself with — and read the active\n * key from — its parent `<wr-tabs>` host.\n *\n * @internal\n */\nexport const WR_TABS = new InjectionToken<WrTabsContext>('WR_TABS');\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 { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n DestroyRef,\n TemplateRef,\n ViewEncapsulation,\n computed,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { randomId } from 'ngwr/utils';\n\nimport { WR_TABS } from './tokens';\n\n/**\n * Single tab. Two modes, decided by inputs:\n *\n * - **Content tab** — projected children render in the parent's content\n * panel when this tab is active.\n * - **Router tab** — set `routerLink` and the tab becomes a link; the\n * parent skips its content panel and the consumer drops a\n * `<router-outlet>` below the tab strip.\n */\n@Component({\n selector: 'wr-tab',\n template: `<ng-template><ng-content /></ng-template>`,\n encapsulation: ViewEncapsulation.None,\n host: { style: 'display:none' },\n})\nexport class WrTab {\n /** Visible label on the tab strip. */\n readonly title = input.required<string>();\n\n /**\n * Stable identifier used by the parent to track the active tab.\n * Auto-generated if omitted.\n */\n readonly key = input<string>(randomId('wr-tab'));\n\n /** Router target — when set, the tab becomes a `[routerLink]` link. */\n readonly routerLink = input<string | readonly string[] | null>(null);\n\n /** Disable the tab. */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Tab content template; portaled into the parent's panel area. @internal */\n readonly contentTpl = viewChild.required(TemplateRef);\n\n private readonly parent = inject(WR_TABS, { optional: true });\n private readonly destroyRef = inject(DestroyRef);\n\n protected readonly isRouter = computed(() => this.routerLink() !== null);\n\n constructor() {\n if (this.parent) {\n // Both calls run with the input signals still on their defaults — a\n // constructor is too early for `key()` to be the consumer's value. The\n // parent tracks tabs through `contentChildren` and seeds `active` from\n // there; these stay as the declared lifecycle contract.\n this.parent.register({ key: this.key(), routerLink: this.routerLink() });\n this.destroyRef.onDestroy(() => this.parent?.unregister(this.key()));\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 { Directionality } from '@angular/cdk/bidi';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n type ElementRef,\n ViewEncapsulation,\n afterNextRender,\n computed,\n contentChildren,\n effect,\n forwardRef,\n inject,\n input,\n model,\n signal,\n viewChild,\n} from '@angular/core';\nimport { RouterLink, RouterLinkActive } from '@angular/router';\n\nimport { WrTab } from './tab';\nimport { WR_TABS, type WrTabsContext } from './tokens';\n\n/**\n * Tabbed container. Two modes depending on the child `<wr-tab>`\n * definitions:\n *\n * - **Content** — children project body content; the parent renders an\n * active-tab panel automatically.\n * - **Router** — any child with `routerLink` switches the whole strip\n * into router mode; the parent skips its panel so the consumer can\n * drop a `<router-outlet>` after it.\n *\n * @example\n * ```html\n * <!-- content tabs -->\n * <wr-tabs [(active)]=\"key\">\n * <wr-tab key=\"one\" title=\"One\">First panel</wr-tab>\n * <wr-tab key=\"two\" title=\"Two\">Second panel</wr-tab>\n * </wr-tabs>\n *\n * <!-- router tabs -->\n * <wr-tabs>\n * <wr-tab title=\"Overview\" routerLink=\"overview\" />\n * <wr-tab title=\"Details\" routerLink=\"details\" />\n * </wr-tabs>\n * <router-outlet />\n * ```\n *\n * @see https://ngwr.dev/reference/components/tabs\n */\n@Component({\n selector: 'wr-tabs',\n templateUrl: './tabs.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-tabs',\n '[class.wr-tabs--fade-start]': 'canScrollStart()',\n '[class.wr-tabs--fade-end]': 'canScrollEnd()',\n '[class.wr-tabs--sm]': \"size() === 'sm'\",\n '[class.wr-tabs--lg]': \"size() === 'lg'\",\n },\n imports: [NgTemplateOutlet, RouterLink, RouterLinkActive],\n providers: [\n {\n provide: WR_TABS,\n // eslint-disable-next-line @angular-eslint/no-forward-ref\n useExisting: forwardRef(() => WrTabs),\n },\n ],\n})\nexport class WrTabs implements WrTabsContext {\n /** Two-way bindable active tab key (content mode). */\n readonly active = model<string | null>(null);\n\n /** Visual size variant. */\n readonly size = input<'sm' | 'md' | 'lg'>('md');\n\n protected readonly tabs = contentChildren(WrTab);\n\n private readonly dir = inject(Directionality, { optional: true });\n\n /**\n * Reading direction of the strip. `Directionality` is root-provided, so this\n * always resolves — `optional` only guards a consumer who has deliberately\n * torn the provider out. It reads the document once at construction; a\n * subtree that overrides the direction does it with the CDK's `Dir`\n * directive, which writes `valueSignal`, so the read stays reactive.\n */\n private readonly isRtl = computed(() => this.dir?.valueSignal() === 'rtl');\n\n /** When any child has a routerLink, the whole strip switches to router mode. */\n protected readonly isRouter = computed(() => this.tabs().some(t => t.routerLink() !== null));\n\n /** Content-mode: the tab whose key matches `active()` (or the first tab if none yet). */\n protected readonly activeTab = computed(() => {\n const key = this.active();\n const tabs = this.tabs();\n if (tabs.length === 0) return null;\n return tabs.find(t => t.key() === key) ?? tabs[0];\n });\n\n // WrTabsContext\n\n activate(key: string): void {\n this.active.set(key);\n }\n\n register(): void {\n // Deliberately a no-op. A `WrTab` calls this from its own constructor,\n // where its `key` signal input is not bound yet — so it would report the\n // generated default and this parent would write THAT into `active`. The\n // effect below seeds from `tabs()`, by which point the inputs are real.\n }\n\n unregister(): void {\n // No-op: contentChildren handles removal.\n }\n\n // Template handlers\n\n protected readonly stripRef = viewChild<ElementRef<HTMLElement>>('strip');\n\n /** Whether the strip is scrolled away from its start / end — drives the edge fades. */\n protected readonly canScrollStart = signal(false);\n protected readonly canScrollEnd = signal(false);\n\n constructor() {\n // Seed `active` with the first tab's key, once the content children exist\n // and their inputs are bound. Without this the model writes back a\n // generated id like `wr-tab-b1crta5aix0v`: the strip still highlighted the\n // right tab, because `activeTab()` falls back to `tabs[0]`, so the only\n // visible symptom was a two-way binding holding a key the consumer had\n // never heard of.\n effect(() => {\n const tabs = this.tabs();\n if (tabs.length > 0 && this.active() === null) this.active.set(tabs[0].key());\n });\n\n const destroyRef = inject(DestroyRef);\n afterNextRender(() => {\n this.updateFades();\n const el = this.stripRef()?.nativeElement;\n if (!el || typeof ResizeObserver === 'undefined') return;\n // Re-evaluate when the strip's width changes (e.g. the container narrows\n // and the tabs start overflowing).\n const ro = new ResizeObserver(() => this.updateFades());\n ro.observe(el);\n destroyRef.onDestroy(() => ro.disconnect());\n });\n }\n\n /** Recompute which edges can scroll, from the strip's scroll metrics. @internal */\n protected updateFades(): void {\n const el = this.stripRef()?.nativeElement;\n if (!el) return;\n const max = el.scrollWidth - el.clientWidth;\n // Distance from the INLINE start, which is what the two fades mean.\n // `scrollLeft` is 0 at the inline start in BOTH directions and counts away\n // from it — up in LTR, DOWN into negatives under `dir=\"rtl\"` (the CSSOM\n // model every engine Angular 22 supports now implements; the legacy\n // \"reversed\"/\"normal\" RTL conventions are gone). Read raw, the RTL value\n // said \"never scrolled from the start, always more to come\" at every\n // position: the end fade was pinned on and the start fade never appeared.\n //\n // Clamped rather than `Math.abs`, because an elastic overscroll reports a\n // value of the WRONG sign at the start edge — a rubber-band past the start\n // is still the start, and `abs` would light the start fade there in LTR too.\n const scrolled = this.isRtl() ? Math.max(-el.scrollLeft, 0) : Math.max(el.scrollLeft, 0);\n this.canScrollStart.set(scrolled > 1);\n this.canScrollEnd.set(max > 1 && scrolled < max - 1);\n }\n\n protected onTabClick(tab: WrTab): void {\n if (tab.disabled()) return;\n if (!this.isRouter()) this.activate(tab.key());\n }\n\n /** Header id for `aria-labelledby` on the panel. @internal */\n headerId(key: string): string {\n return `wr-tab-${key}-header`;\n }\n\n /** Panel id for `aria-controls` on the tab header. @internal */\n panelId(key: string): string {\n return `wr-tab-${key}-panel`;\n }\n\n /** ArrowLeft/Right/Home/End keyboard navigation on the tab strip. */\n protected onStripKeydown(event: KeyboardEvent): void {\n const tabs = this.tabs().filter(t => !t.disabled());\n if (tabs.length === 0) return;\n const active = this.activeTab();\n const idx = active ? tabs.indexOf(active) : -1;\n // The two neighbours in DOM order, wrapping at the ends.\n const forward = idx < tabs.length - 1 ? idx + 1 : 0;\n const backward = idx > 0 ? idx - 1 : tabs.length - 1;\n const rtl = this.isRtl();\n let nextIdx: number;\n\n switch (event.key) {\n // Arrow keys follow VISUAL order (WAI-ARIA APG). The strip is mirrored\n // under `dir=\"rtl\"`, so ArrowRight moves toward the visual right, which is\n // the PREVIOUS tab there.\n case 'ArrowRight':\n nextIdx = rtl ? backward : forward;\n break;\n case 'ArrowLeft':\n nextIdx = rtl ? forward : backward;\n break;\n // Home / End name a position in the tab list — first and last — not a\n // physical edge, so they read the same in both directions.\n case 'Home':\n nextIdx = 0;\n break;\n case 'End':\n nextIdx = tabs.length - 1;\n break;\n default:\n return;\n }\n\n event.preventDefault();\n const next = tabs[nextIdx];\n if (!this.isRouter()) this.activate(next.key());\n // Move focus to the corresponding header element.\n const headerEl = this.stripRef()?.nativeElement.querySelector<HTMLElement>(\n `#${CSS.escape(this.headerId(next.key()))}`\n );\n headerEl?.focus();\n }\n}\n","<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n #strip\n class=\"wr-tabs__strip\"\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n (keydown)=\"onStripKeydown($event)\"\n (scroll)=\"updateFades()\"\n>\n @for (tab of tabs(); track tab.key()) {\n @if (tab.routerLink(); as link) {\n <a\n class=\"wr-tabs__tab\"\n role=\"tab\"\n routerLinkActive=\"wr-tabs__tab--active\"\n [attr.id]=\"headerId(tab.key())\"\n [routerLink]=\"tab.disabled() ? null : link\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-disabled]=\"tab.disabled() ? true : null\"\n [attr.tabindex]=\"tab.disabled() ? -1 : 0\"\n >\n {{ tab.title() }}\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.aria-controls]=\"panelId(tab.key())\"\n [attr.tabindex]=\"tab === activeTab() && !tab.disabled() ? 0 : -1\"\n [class.wr-tabs__tab--active]=\"tab === activeTab()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-selected]=\"tab === activeTab()\"\n [disabled]=\"tab.disabled()\"\n (click)=\"onTabClick(tab)\"\n >\n {{ tab.title() }}\n </button>\n }\n }\n</div>\n\n@if (!isRouter() && activeTab(); as tab) {\n <div\n class=\"wr-tabs__panel\"\n role=\"tabpanel\"\n tabindex=\"0\"\n [attr.id]=\"panelId(tab.key())\"\n [attr.aria-labelledby]=\"headerId(tab.key())\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.contentTpl()\" />\n </div>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-tabs.mjs","sources":["../../../projects/lib/tabs/tokens/tabs.token.ts","../../../projects/lib/tabs/tab.ts","../../../projects/lib/tabs/tabs.ts","../../../projects/lib/tabs/tabs.html","../../../projects/lib/tabs/ngwr-tabs.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 { InjectionToken, type Signal } from '@angular/core';\n\n/**\n * Contract a `<wr-tab>` uses to talk to its parent `<wr-tabs>`.\n *\n * @internal\n */\nexport interface WrTabsContext {\n /** Currently active tab key (component-managed only). */\n readonly active: Signal<string | null>;\n /** Activate a tab by key (component-managed only). */\n activate(key: string): void;\n /** Register a child tab so the parent can pick a default. */\n register(tab: { key: string; routerLink: unknown }): void;\n /** Unregister on destroy. */\n unregister(key: string): void;\n}\n\n/**\n * Token a `<wr-tab>` injects to register itself with — and read the active\n * key from — its parent `<wr-tabs>` host.\n *\n * @internal\n */\nexport const WR_TABS = new InjectionToken<WrTabsContext>('WR_TABS');\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 { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport {\n Component,\n DestroyRef,\n TemplateRef,\n ViewEncapsulation,\n computed,\n inject,\n input,\n viewChild,\n} from '@angular/core';\n\nimport { randomId } from 'ngwr/utils';\n\nimport { WR_TABS } from './tokens';\n\n/**\n * Single tab. Two modes, decided by inputs:\n *\n * - **Content tab** — projected children render in the parent's content\n * panel when this tab is active.\n * - **Router tab** — set `routerLink` and the tab becomes a link; the\n * parent skips its content panel and the consumer drops a\n * `<router-outlet>` below the tab strip.\n */\n@Component({\n selector: 'wr-tab',\n template: `<ng-template><ng-content /></ng-template>`,\n encapsulation: ViewEncapsulation.None,\n host: { style: 'display:none' },\n})\nexport class WrTab {\n /** Visible label on the tab strip. */\n readonly title = input.required<string>();\n\n /**\n * Stable identifier used by the parent to track the active tab.\n * Auto-generated if omitted.\n */\n readonly key = input<string>(randomId('wr-tab'));\n\n /** Router target — when set, the tab becomes a `[routerLink]` link. */\n readonly routerLink = input<string | readonly string[] | null>(null);\n\n /** Disable the tab. */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /** Tab content template; portaled into the parent's panel area. @internal */\n readonly contentTpl = viewChild.required(TemplateRef);\n\n private readonly parent = inject(WR_TABS, { optional: true });\n private readonly destroyRef = inject(DestroyRef);\n\n protected readonly isRouter = computed(() => this.routerLink() !== null);\n\n constructor() {\n if (this.parent) {\n // Both calls run with the input signals still on their defaults — a\n // constructor is too early for `key()` to be the consumer's value. The\n // parent tracks tabs through `contentChildren` and seeds `active` from\n // there; these stay as the declared lifecycle contract.\n this.parent.register({ key: this.key(), routerLink: this.routerLink() });\n this.destroyRef.onDestroy(() => this.parent?.unregister(this.key()));\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 { Directionality } from '@angular/cdk/bidi';\nimport { NgTemplateOutlet } from '@angular/common';\nimport {\n Component,\n DestroyRef,\n type ElementRef,\n ViewEncapsulation,\n afterNextRender,\n computed,\n contentChildren,\n effect,\n forwardRef,\n inject,\n input,\n model,\n signal,\n viewChild,\n} from '@angular/core';\nimport { RouterLink, RouterLinkActive } from '@angular/router';\n\nimport { WrTab } from './tab';\nimport { WR_TABS, type WrTabsContext } from './tokens';\n\n/**\n * Tabbed container. Two modes depending on the child `<wr-tab>`\n * definitions:\n *\n * - **Content** — children project body content; the parent renders an\n * active-tab panel automatically.\n * - **Router** — any child with `routerLink` switches the whole strip\n * into router mode; the parent skips its panel so the consumer can\n * drop a `<router-outlet>` after it.\n *\n * @example\n * ```html\n * <!-- content tabs -->\n * <wr-tabs [(active)]=\"key\">\n * <wr-tab key=\"one\" title=\"One\">First panel</wr-tab>\n * <wr-tab key=\"two\" title=\"Two\">Second panel</wr-tab>\n * </wr-tabs>\n *\n * <!-- router tabs -->\n * <wr-tabs>\n * <wr-tab title=\"Overview\" routerLink=\"overview\" />\n * <wr-tab title=\"Details\" routerLink=\"details\" />\n * </wr-tabs>\n * <router-outlet />\n * ```\n *\n * @see https://ngwr.dev/reference/components/tabs\n */\n@Component({\n selector: 'wr-tabs',\n templateUrl: './tabs.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n class: 'wr-tabs',\n '[class.wr-tabs--fade-start]': 'canScrollStart()',\n '[class.wr-tabs--fade-end]': 'canScrollEnd()',\n '[class.wr-tabs--sm]': \"size() === 'sm'\",\n '[class.wr-tabs--lg]': \"size() === 'lg'\",\n },\n imports: [NgTemplateOutlet, RouterLink, RouterLinkActive],\n providers: [\n {\n provide: WR_TABS,\n // eslint-disable-next-line @angular-eslint/no-forward-ref\n useExisting: forwardRef(() => WrTabs),\n },\n ],\n})\nexport class WrTabs implements WrTabsContext {\n /** Two-way bindable active tab key (content mode). */\n readonly active = model<string | null>(null);\n\n /** Visual size variant. */\n readonly size = input<'sm' | 'md' | 'lg'>('md');\n\n protected readonly tabs = contentChildren(WrTab);\n\n private readonly dir = inject(Directionality, { optional: true });\n\n /**\n * Reading direction of the strip. `Directionality` is root-provided, so this\n * always resolves — `optional` only guards a consumer who has deliberately\n * torn the provider out. It reads the document once at construction; a\n * subtree that overrides the direction does it with the CDK's `Dir`\n * directive, which writes `valueSignal`, so the read stays reactive.\n */\n private readonly isRtl = computed(() => this.dir?.valueSignal() === 'rtl');\n\n /** When any child has a routerLink, the whole strip switches to router mode. */\n protected readonly isRouter = computed(() => this.tabs().some(t => t.routerLink() !== null));\n\n /** Content-mode: the tab whose key matches `active()` (or the first tab if none yet). */\n protected readonly activeTab = computed(() => {\n const key = this.active();\n const tabs = this.tabs();\n if (tabs.length === 0) return null;\n return tabs.find(t => t.key() === key) ?? tabs[0];\n });\n\n // WrTabsContext\n\n activate(key: string): void {\n this.active.set(key);\n }\n\n register(): void {\n // Deliberately a no-op. A `WrTab` calls this from its own constructor,\n // where its `key` signal input is not bound yet — so it would report the\n // generated default and this parent would write THAT into `active`. The\n // effect below seeds from `tabs()`, by which point the inputs are real.\n }\n\n unregister(): void {\n // No-op: contentChildren handles removal.\n }\n\n // Template handlers\n\n protected readonly stripRef = viewChild<ElementRef<HTMLElement>>('strip');\n\n /** Whether the strip is scrolled away from its start / end — drives the edge fades. */\n protected readonly canScrollStart = signal(false);\n protected readonly canScrollEnd = signal(false);\n\n constructor() {\n // Seed `active` with the first tab's key, once the content children exist\n // and their inputs are bound. Without this the model writes back a\n // generated id like `wr-tab-b1crta5aix0v`: the strip still highlighted the\n // right tab, because `activeTab()` falls back to `tabs[0]`, so the only\n // visible symptom was a two-way binding holding a key the consumer had\n // never heard of.\n effect(() => {\n const tabs = this.tabs();\n if (tabs.length > 0 && this.active() === null) this.active.set(tabs[0].key());\n });\n\n const destroyRef = inject(DestroyRef);\n afterNextRender(() => {\n this.updateFades();\n const el = this.stripRef()?.nativeElement;\n if (!el || typeof ResizeObserver === 'undefined') return;\n // Re-evaluate when the strip's width changes (e.g. the container narrows\n // and the tabs start overflowing).\n const ro = new ResizeObserver(() => this.updateFades());\n ro.observe(el);\n destroyRef.onDestroy(() => ro.disconnect());\n });\n }\n\n /** Recompute which edges can scroll, from the strip's scroll metrics. @internal */\n protected updateFades(): void {\n const el = this.stripRef()?.nativeElement;\n if (!el) return;\n const max = el.scrollWidth - el.clientWidth;\n // Distance from the INLINE start, which is what the two fades mean.\n // `scrollLeft` is 0 at the inline start in BOTH directions and counts away\n // from it — up in LTR, DOWN into negatives under `dir=\"rtl\"` (the CSSOM\n // model every engine Angular 22 supports now implements; the legacy\n // \"reversed\"/\"normal\" RTL conventions are gone). Read raw, the RTL value\n // said \"never scrolled from the start, always more to come\" at every\n // position: the end fade was pinned on and the start fade never appeared.\n //\n // Clamped rather than `Math.abs`, because an elastic overscroll reports a\n // value of the WRONG sign at the start edge — a rubber-band past the start\n // is still the start, and `abs` would light the start fade there in LTR too.\n const scrolled = this.isRtl() ? Math.max(-el.scrollLeft, 0) : Math.max(el.scrollLeft, 0);\n this.canScrollStart.set(scrolled > 1);\n this.canScrollEnd.set(max > 1 && scrolled < max - 1);\n }\n\n protected onTabClick(tab: WrTab): void {\n if (tab.disabled()) return;\n if (!this.isRouter()) this.activate(tab.key());\n }\n\n /** Header id for `aria-labelledby` on the panel. @internal */\n headerId(key: string): string {\n return `wr-tab-${key}-header`;\n }\n\n /** Panel id for `aria-controls` on the tab header. @internal */\n panelId(key: string): string {\n return `wr-tab-${key}-panel`;\n }\n\n /** ArrowLeft/Right/Home/End keyboard navigation on the tab strip. */\n protected onStripKeydown(event: KeyboardEvent): void {\n const tabs = this.tabs().filter(t => !t.disabled());\n if (tabs.length === 0) return;\n\n // Step from where FOCUS is, not from what is active.\n //\n // In router mode nothing ever moves `active` — `onTabClick` and this\n // handler both gate `activate()` on `!isRouter()`, because the route is\n // what selects a tab there. Reading the neighbour off `activeTab()`\n // therefore recomputed the same `idx` on every press, and ArrowRight\n // walked from the first tab to the second and stayed there for the rest of\n // the session. In manual mode focus and activation move together, so the\n // two readings agree and nothing changes.\n const strip = this.stripRef()?.nativeElement;\n const focused = strip?.ownerDocument.activeElement;\n const focusedIdx =\n focused instanceof HTMLElement && strip?.contains(focused)\n ? tabs.findIndex(t => this.headerId(t.key()) === focused.id)\n : -1;\n const active = this.activeTab();\n const idx = focusedIdx >= 0 ? focusedIdx : active ? tabs.indexOf(active) : -1;\n // The two neighbours in DOM order, wrapping at the ends.\n const forward = idx < tabs.length - 1 ? idx + 1 : 0;\n const backward = idx > 0 ? idx - 1 : tabs.length - 1;\n const rtl = this.isRtl();\n let nextIdx: number;\n\n switch (event.key) {\n // Arrow keys follow VISUAL order (WAI-ARIA APG). The strip is mirrored\n // under `dir=\"rtl\"`, so ArrowRight moves toward the visual right, which is\n // the PREVIOUS tab there.\n case 'ArrowRight':\n nextIdx = rtl ? backward : forward;\n break;\n case 'ArrowLeft':\n nextIdx = rtl ? forward : backward;\n break;\n // Home / End name a position in the tab list — first and last — not a\n // physical edge, so they read the same in both directions.\n case 'Home':\n nextIdx = 0;\n break;\n case 'End':\n nextIdx = tabs.length - 1;\n break;\n default:\n return;\n }\n\n event.preventDefault();\n const next = tabs[nextIdx];\n if (!this.isRouter()) this.activate(next.key());\n // Move focus to the corresponding header element.\n const headerEl = this.stripRef()?.nativeElement.querySelector<HTMLElement>(\n `#${CSS.escape(this.headerId(next.key()))}`\n );\n headerEl?.focus();\n }\n}\n","<!-- eslint-disable-next-line @angular-eslint/template/interactive-supports-focus -->\n<div\n #strip\n class=\"wr-tabs__strip\"\n role=\"tablist\"\n aria-orientation=\"horizontal\"\n (keydown)=\"onStripKeydown($event)\"\n (scroll)=\"updateFades()\"\n>\n @for (tab of tabs(); track tab.key()) {\n @if (tab.routerLink(); as link) {\n <a\n class=\"wr-tabs__tab\"\n role=\"tab\"\n routerLinkActive=\"wr-tabs__tab--active\"\n [attr.id]=\"headerId(tab.key())\"\n [routerLink]=\"tab.disabled() ? null : link\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-disabled]=\"tab.disabled() ? true : null\"\n [attr.tabindex]=\"tab.disabled() ? -1 : 0\"\n >\n {{ tab.title() }}\n </a>\n } @else {\n <button\n type=\"button\"\n class=\"wr-tabs__tab\"\n role=\"tab\"\n [attr.id]=\"headerId(tab.key())\"\n [attr.aria-controls]=\"panelId(tab.key())\"\n [attr.tabindex]=\"tab === activeTab() && !tab.disabled() ? 0 : -1\"\n [class.wr-tabs__tab--active]=\"tab === activeTab()\"\n [class.wr-tabs__tab--disabled]=\"tab.disabled()\"\n [attr.aria-selected]=\"tab === activeTab()\"\n [disabled]=\"tab.disabled()\"\n (click)=\"onTabClick(tab)\"\n >\n {{ tab.title() }}\n </button>\n }\n }\n</div>\n\n@if (!isRouter() && activeTab(); as tab) {\n <div\n class=\"wr-tabs__panel\"\n role=\"tabpanel\"\n tabindex=\"0\"\n [attr.id]=\"panelId(tab.key())\"\n [attr.aria-labelledby]=\"headerId(tab.key())\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.contentTpl()\" />\n </div>\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
*
|
|
6
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
7
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
8
|
+
*/
|
|
9
|
+
/** The overlay `[glare]` appends — `aria-hidden`, and the only node the directive adds. */
|
|
10
|
+
const GLARE = '.wr-tilt-glare';
|
|
11
|
+
/** Pull one `name(value)` out of a transform string. */
|
|
12
|
+
function fromTransform(transform, fn) {
|
|
13
|
+
const match = new RegExp(`${fn}\\(([-\\d.]+)`).exec(transform);
|
|
14
|
+
return match ? Number.parseFloat(match[1]) : null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Test harness for `[wrTilt]` and for `<wr-tilt-card>`, which applies it as a host
|
|
18
|
+
* directive — one class, because they are one behaviour on two hosts.
|
|
19
|
+
*
|
|
20
|
+
* **The tilt itself needs a box, and a unit test has none.** The directive turns a
|
|
21
|
+
* pointer position into two rotations by dividing by `getBoundingClientRect()`, which
|
|
22
|
+
* is 0×0 in jsdom, so a move dispatched at a component that has not been given
|
|
23
|
+
* dimensions writes `rotateX(NaNdeg)`. {@link movePointerTo} is still here because the
|
|
24
|
+
* fix is one line in the SPEC — stub the host's rect, the way
|
|
25
|
+
* `projects/lib/image-cropper/testing` documents — and because the two things worth
|
|
26
|
+
* asserting on this component are only reachable through it: that a move writes a
|
|
27
|
+
* transform at all, and that {@link leave} takes it away again.
|
|
28
|
+
*
|
|
29
|
+
* **{@link isFlat} is the reduced-motion assertion.** `onMove` returns before touching
|
|
30
|
+
* the host when the user asked for less motion, so "no inline transform after a move"
|
|
31
|
+
* is the whole contract, and it is one of the few reduced-motion behaviours in the
|
|
32
|
+
* animation set that a test can actually see — most of the others live in a `@media`
|
|
33
|
+
* block. Provide the `WrPlatform` stub and assert this.
|
|
34
|
+
*
|
|
35
|
+
* There is no `getGlareOpacity()` or `isGlareVisible()`: the overlay's gradient, its
|
|
36
|
+
* blend mode and its fade all live in the stylesheet. And no `getMaxTilt()` /
|
|
37
|
+
* `getPerspectiveInput()` — those would echo an input back rather than report what the
|
|
38
|
+
* directive wrote; {@link getPerspective} and {@link getScale} read the transform the
|
|
39
|
+
* host is actually carrying, which is the value that can be wrong.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const card = await loader.getHarness(WrTiltHarness);
|
|
44
|
+
* expect(await card.isFlat()).toBe(true);
|
|
45
|
+
*
|
|
46
|
+
* await card.movePointerTo(150, 50); // the spec stubbed the rect first
|
|
47
|
+
* expect((await card.getTilt())?.rotateY).toBeGreaterThan(0);
|
|
48
|
+
*
|
|
49
|
+
* await card.leave();
|
|
50
|
+
* expect(await card.isFlat()).toBe(true);
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @see https://ngwr.dev/guides/testing
|
|
54
|
+
*/
|
|
55
|
+
class WrTiltHarness extends ComponentHarness {
|
|
56
|
+
/** The directive on a consumer's element, and the card that hosts it. */
|
|
57
|
+
static hostSelector = '[wrTilt], wr-tilt-card';
|
|
58
|
+
/** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
|
|
59
|
+
static with(options = {}) {
|
|
60
|
+
return new HarnessPredicate(WrTiltHarness, options)
|
|
61
|
+
.addOption('glare', options.glare, async (harness, glare) => (await harness.hasGlare()) === glare)
|
|
62
|
+
.addOption('flat', options.flat, async (harness, flat) => (await harness.isFlat()) === flat);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Whether the host is at rest — no inline transform at all.
|
|
66
|
+
*
|
|
67
|
+
* True before the first move, after {@link leave}, and for the whole life of a card
|
|
68
|
+
* whose user asked for less motion. It is the difference between "flat" and
|
|
69
|
+
* "rotated by zero": the directive CLEARS the property rather than writing an
|
|
70
|
+
* identity transform, so an empty string is the state and `rotateX(0deg)` would be
|
|
71
|
+
* a component that tilted and came back the long way.
|
|
72
|
+
*/
|
|
73
|
+
async isFlat() {
|
|
74
|
+
return (await this.getTransform()) === '';
|
|
75
|
+
}
|
|
76
|
+
/** The inline `transform` exactly as written, or `''` at rest. */
|
|
77
|
+
async getTransform() {
|
|
78
|
+
return this.inlineStyle('transform');
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The two rotations the host is holding, in degrees, or `null` while it is flat.
|
|
82
|
+
*
|
|
83
|
+
* Parsed from the transform rather than recomputed: the harness has no more access
|
|
84
|
+
* to the pointer maths than a consumer does, and the string is what the compositor
|
|
85
|
+
* will read.
|
|
86
|
+
*/
|
|
87
|
+
async getTilt() {
|
|
88
|
+
const transform = await this.getTransform();
|
|
89
|
+
if (transform === '')
|
|
90
|
+
return null;
|
|
91
|
+
const rotateX = fromTransform(transform, 'rotateX');
|
|
92
|
+
const rotateY = fromTransform(transform, 'rotateY');
|
|
93
|
+
return rotateX === null || rotateY === null ? null : { rotateX, rotateY };
|
|
94
|
+
}
|
|
95
|
+
/** The perspective the transform was written with, in pixels, or `null` while flat. */
|
|
96
|
+
async getPerspective() {
|
|
97
|
+
return fromTransform(await this.getTransform(), 'perspective');
|
|
98
|
+
}
|
|
99
|
+
/** The scale the transform was written with, or `null` while flat. */
|
|
100
|
+
async getScale() {
|
|
101
|
+
return fromTransform(await this.getTransform(), 'scale');
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Whether the host was set up to hold children in 3-D.
|
|
105
|
+
*
|
|
106
|
+
* Written once in the constructor, so it is true from first render and stays true —
|
|
107
|
+
* which makes it the cheapest evidence that the directive is applied at all, on a
|
|
108
|
+
* host where every other read is `''` until something moves.
|
|
109
|
+
*/
|
|
110
|
+
async isPreserve3d() {
|
|
111
|
+
return (await this.inlineStyle('transform-style')) === 'preserve-3d';
|
|
112
|
+
}
|
|
113
|
+
/** Whether the glare overlay is installed (`[glare]`). */
|
|
114
|
+
async hasGlare() {
|
|
115
|
+
return (await this.locatorForOptional(GLARE)()) !== null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Whether the glare is kept out of the accessibility tree.
|
|
119
|
+
*
|
|
120
|
+
* It is a decorative highlight with no text, so a screen reader must not stop on
|
|
121
|
+
* it. Asserted rather than assumed because the overlay is created imperatively —
|
|
122
|
+
* there is no template line for a reader to check.
|
|
123
|
+
*/
|
|
124
|
+
async isGlareDecorative() {
|
|
125
|
+
const glare = await this.locatorForOptional(GLARE)();
|
|
126
|
+
return glare !== null && (await glare.getAttribute('aria-hidden')) === 'true';
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Where the glare is pointing, as the two percentages the directive writes, or
|
|
130
|
+
* `null` when there is no glare or nothing has moved yet.
|
|
131
|
+
*
|
|
132
|
+
* Cleared when the overlay is removed: a card that has given `[glare]` back should
|
|
133
|
+
* not look like it is still tracking a pointer.
|
|
134
|
+
*/
|
|
135
|
+
async getGlarePosition() {
|
|
136
|
+
const x = await this.inlineStyle('--wr-tilt-glare-x');
|
|
137
|
+
const y = await this.inlineStyle('--wr-tilt-glare-y');
|
|
138
|
+
return x === '' || y === '' ? null : { x, y };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Move a pointer over the host.
|
|
142
|
+
*
|
|
143
|
+
* **Stub the host's rect in your spec first.** The directive divides the pointer
|
|
144
|
+
* offset by the host's width and height, and jsdom measures every element at 0×0,
|
|
145
|
+
* so without a box the transform comes out as `NaN` — which is not a failure the
|
|
146
|
+
* component would ever produce in a browser. One line does it:
|
|
147
|
+
*
|
|
148
|
+
* ```ts
|
|
149
|
+
* host.getBoundingClientRect = () => ({ left: 0, top: 0, width: 200, height: 100 }) as DOMRect;
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
async movePointerTo(clientX, clientY) {
|
|
153
|
+
await (await this.host()).dispatchEvent('pointermove', { clientX, clientY });
|
|
154
|
+
}
|
|
155
|
+
/** Take the pointer off the host — which clears the transform and settles it flat. */
|
|
156
|
+
async leave() {
|
|
157
|
+
await (await this.host()).dispatchEvent('pointerleave');
|
|
158
|
+
}
|
|
159
|
+
/** The projected content's text, trimmed. */
|
|
160
|
+
async getContentText() {
|
|
161
|
+
return (await this.host()).text();
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* One inline declaration, read off the `style` ATTRIBUTE.
|
|
165
|
+
*
|
|
166
|
+
* Not `getCssValue`, which is `getComputedStyle`: that resolves the stylesheet as
|
|
167
|
+
* well, so `transform` would come back as the resolved matrix in a browser and
|
|
168
|
+
* `--wr-tilt-glare-x` would answer with the sheet's own default. The attribute holds
|
|
169
|
+
* what the directive wrote and nothing else, which is the only thing worth pinning.
|
|
170
|
+
*/
|
|
171
|
+
async inlineStyle(property) {
|
|
172
|
+
const style = (await (await this.host()).getAttribute('style')) ?? '';
|
|
173
|
+
for (const declaration of style.split(';')) {
|
|
174
|
+
const colon = declaration.indexOf(':');
|
|
175
|
+
if (colon === -1)
|
|
176
|
+
continue;
|
|
177
|
+
if (declaration.slice(0, colon).trim() !== property)
|
|
178
|
+
continue;
|
|
179
|
+
return declaration.slice(colon + 1).trim();
|
|
180
|
+
}
|
|
181
|
+
return '';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Generated bundle index. Do not edit.
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
export { WrTiltHarness };
|
|
190
|
+
//# sourceMappingURL=ngwr-tilt-card-testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngwr-tilt-card-testing.mjs","sources":["../../../projects/lib/tilt-card/testing/wr-tilt-harness.ts","../../../projects/lib/tilt-card/testing/ngwr-tilt-card-testing.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 { WrTiltHarnessFilters, WrTiltRotation } from './interfaces';\n\n/** The overlay `[glare]` appends — `aria-hidden`, and the only node the directive adds. */\nconst GLARE = '.wr-tilt-glare';\n\n/** Pull one `name(value)` out of a transform string. */\nfunction fromTransform(transform: string, fn: string): number | null {\n const match = new RegExp(`${fn}\\\\(([-\\\\d.]+)`).exec(transform);\n return match ? Number.parseFloat(match[1]) : null;\n}\n\n/**\n * Test harness for `[wrTilt]` and for `<wr-tilt-card>`, which applies it as a host\n * directive — one class, because they are one behaviour on two hosts.\n *\n * **The tilt itself needs a box, and a unit test has none.** The directive turns a\n * pointer position into two rotations by dividing by `getBoundingClientRect()`, which\n * is 0×0 in jsdom, so a move dispatched at a component that has not been given\n * dimensions writes `rotateX(NaNdeg)`. {@link movePointerTo} is still here because the\n * fix is one line in the SPEC — stub the host's rect, the way\n * `projects/lib/image-cropper/testing` documents — and because the two things worth\n * asserting on this component are only reachable through it: that a move writes a\n * transform at all, and that {@link leave} takes it away again.\n *\n * **{@link isFlat} is the reduced-motion assertion.** `onMove` returns before touching\n * the host when the user asked for less motion, so \"no inline transform after a move\"\n * is the whole contract, and it is one of the few reduced-motion behaviours in the\n * animation set that a test can actually see — most of the others live in a `@media`\n * block. Provide the `WrPlatform` stub and assert this.\n *\n * There is no `getGlareOpacity()` or `isGlareVisible()`: the overlay's gradient, its\n * blend mode and its fade all live in the stylesheet. And no `getMaxTilt()` /\n * `getPerspectiveInput()` — those would echo an input back rather than report what the\n * directive wrote; {@link getPerspective} and {@link getScale} read the transform the\n * host is actually carrying, which is the value that can be wrong.\n *\n * @example\n * ```ts\n * const card = await loader.getHarness(WrTiltHarness);\n * expect(await card.isFlat()).toBe(true);\n *\n * await card.movePointerTo(150, 50); // the spec stubbed the rect first\n * expect((await card.getTilt())?.rotateY).toBeGreaterThan(0);\n *\n * await card.leave();\n * expect(await card.isFlat()).toBe(true);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrTiltHarness extends ComponentHarness {\n /** The directive on a consumer's element, and the card that hosts it. */\n static hostSelector = '[wrTilt], wr-tilt-card';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrTiltHarnessFilters = {}): HarnessPredicate<WrTiltHarness> {\n return new HarnessPredicate(WrTiltHarness, options)\n .addOption('glare', options.glare, async (harness, glare) => (await harness.hasGlare()) === glare)\n .addOption('flat', options.flat, async (harness, flat) => (await harness.isFlat()) === flat);\n }\n\n /**\n * Whether the host is at rest — no inline transform at all.\n *\n * True before the first move, after {@link leave}, and for the whole life of a card\n * whose user asked for less motion. It is the difference between \"flat\" and\n * \"rotated by zero\": the directive CLEARS the property rather than writing an\n * identity transform, so an empty string is the state and `rotateX(0deg)` would be\n * a component that tilted and came back the long way.\n */\n async isFlat(): Promise<boolean> {\n return (await this.getTransform()) === '';\n }\n\n /** The inline `transform` exactly as written, or `''` at rest. */\n async getTransform(): Promise<string> {\n return this.inlineStyle('transform');\n }\n\n /**\n * The two rotations the host is holding, in degrees, or `null` while it is flat.\n *\n * Parsed from the transform rather than recomputed: the harness has no more access\n * to the pointer maths than a consumer does, and the string is what the compositor\n * will read.\n */\n async getTilt(): Promise<WrTiltRotation | null> {\n const transform = await this.getTransform();\n if (transform === '') return null;\n\n const rotateX = fromTransform(transform, 'rotateX');\n const rotateY = fromTransform(transform, 'rotateY');\n return rotateX === null || rotateY === null ? null : { rotateX, rotateY };\n }\n\n /** The perspective the transform was written with, in pixels, or `null` while flat. */\n async getPerspective(): Promise<number | null> {\n return fromTransform(await this.getTransform(), 'perspective');\n }\n\n /** The scale the transform was written with, or `null` while flat. */\n async getScale(): Promise<number | null> {\n return fromTransform(await this.getTransform(), 'scale');\n }\n\n /**\n * Whether the host was set up to hold children in 3-D.\n *\n * Written once in the constructor, so it is true from first render and stays true —\n * which makes it the cheapest evidence that the directive is applied at all, on a\n * host where every other read is `''` until something moves.\n */\n async isPreserve3d(): Promise<boolean> {\n return (await this.inlineStyle('transform-style')) === 'preserve-3d';\n }\n\n /** Whether the glare overlay is installed (`[glare]`). */\n async hasGlare(): Promise<boolean> {\n return (await this.locatorForOptional(GLARE)()) !== null;\n }\n\n /**\n * Whether the glare is kept out of the accessibility tree.\n *\n * It is a decorative highlight with no text, so a screen reader must not stop on\n * it. Asserted rather than assumed because the overlay is created imperatively —\n * there is no template line for a reader to check.\n */\n async isGlareDecorative(): Promise<boolean> {\n const glare = await this.locatorForOptional(GLARE)();\n return glare !== null && (await glare.getAttribute('aria-hidden')) === 'true';\n }\n\n /**\n * Where the glare is pointing, as the two percentages the directive writes, or\n * `null` when there is no glare or nothing has moved yet.\n *\n * Cleared when the overlay is removed: a card that has given `[glare]` back should\n * not look like it is still tracking a pointer.\n */\n async getGlarePosition(): Promise<{ x: string; y: string } | null> {\n const x = await this.inlineStyle('--wr-tilt-glare-x');\n const y = await this.inlineStyle('--wr-tilt-glare-y');\n return x === '' || y === '' ? null : { x, y };\n }\n\n /**\n * Move a pointer over the host.\n *\n * **Stub the host's rect in your spec first.** The directive divides the pointer\n * offset by the host's width and height, and jsdom measures every element at 0×0,\n * so without a box the transform comes out as `NaN` — which is not a failure the\n * component would ever produce in a browser. One line does it:\n *\n * ```ts\n * host.getBoundingClientRect = () => ({ left: 0, top: 0, width: 200, height: 100 }) as DOMRect;\n * ```\n */\n async movePointerTo(clientX: number, clientY: number): Promise<void> {\n await (await this.host()).dispatchEvent('pointermove', { clientX, clientY });\n }\n\n /** Take the pointer off the host — which clears the transform and settles it flat. */\n async leave(): Promise<void> {\n await (await this.host()).dispatchEvent('pointerleave');\n }\n\n /** The projected content's text, trimmed. */\n async getContentText(): Promise<string> {\n return (await this.host()).text();\n }\n\n /**\n * One inline declaration, read off the `style` ATTRIBUTE.\n *\n * Not `getCssValue`, which is `getComputedStyle`: that resolves the stylesheet as\n * well, so `transform` would come back as the resolved matrix in a browser and\n * `--wr-tilt-glare-x` would answer with the sheet's own default. The attribute holds\n * what the directive wrote and nothing else, which is the only thing worth pinning.\n */\n private async inlineStyle(property: string): Promise<string> {\n const style = (await (await this.host()).getAttribute('style')) ?? '';\n\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 '';\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -35,6 +35,8 @@ class WrTilt {
|
|
|
35
35
|
platform = inject(WrPlatform);
|
|
36
36
|
isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
37
37
|
glareEl = null;
|
|
38
|
+
priorOverflow = '';
|
|
39
|
+
priorPosition = '';
|
|
38
40
|
constructor() {
|
|
39
41
|
const host = this.el.nativeElement;
|
|
40
42
|
host.style.transformStyle = 'preserve-3d';
|
|
@@ -79,6 +81,13 @@ class WrTilt {
|
|
|
79
81
|
if (this.glareEl)
|
|
80
82
|
return;
|
|
81
83
|
const host = this.el.nativeElement;
|
|
84
|
+
// Remembered, not assumed: `overflow` is written unconditionally, so it can be
|
|
85
|
+
// clobbering an author's own inline value, and a blanket `removeProperty` on the
|
|
86
|
+
// way out would delete it. Capturing the empty string is exactly what restores an
|
|
87
|
+
// element that had none — which is the common case, and the one that used to keep
|
|
88
|
+
// `overflow: hidden` for good after `[glare]` was turned back off.
|
|
89
|
+
this.priorOverflow = host.style.overflow;
|
|
90
|
+
this.priorPosition = host.style.position;
|
|
82
91
|
if (getComputedStyle(host).position === 'static')
|
|
83
92
|
host.style.position = 'relative';
|
|
84
93
|
host.style.overflow = 'hidden';
|
|
@@ -89,8 +98,17 @@ class WrTilt {
|
|
|
89
98
|
this.glareEl = glare;
|
|
90
99
|
}
|
|
91
100
|
removeGlare() {
|
|
92
|
-
this.glareEl
|
|
101
|
+
if (!this.glareEl)
|
|
102
|
+
return;
|
|
103
|
+
const host = this.el.nativeElement;
|
|
104
|
+
this.glareEl.remove();
|
|
93
105
|
this.glareEl = null;
|
|
106
|
+
host.style.overflow = this.priorOverflow;
|
|
107
|
+
host.style.position = this.priorPosition;
|
|
108
|
+
// Nothing reads these once the overlay is gone; leaving them behind only makes a
|
|
109
|
+
// destroyed directive look like it is still tracking a pointer.
|
|
110
|
+
host.style.removeProperty('--wr-tilt-glare-x');
|
|
111
|
+
host.style.removeProperty('--wr-tilt-glare-y');
|
|
94
112
|
}
|
|
95
113
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrTilt, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
96
114
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.1.1", type: WrTilt, isStandalone: true, selector: "[wrTilt]", inputs: { maxTilt: { classPropertyName: "maxTilt", publicName: "maxTilt", isSignal: true, isRequired: false, transformFunction: null }, perspective: { classPropertyName: "perspective", publicName: "perspective", isSignal: true, isRequired: false, transformFunction: null }, scale: { classPropertyName: "scale", publicName: "scale", isSignal: true, isRequired: false, transformFunction: null }, glare: { classPropertyName: "glare", publicName: "glare", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "pointermove": "onMove($event)", "pointerleave": "onLeave()" } }, ngImport: i0 });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-tilt-card.mjs","sources":["../../../projects/lib/tilt-card/tilt.ts","../../../projects/lib/tilt-card/tilt-card.ts","../../../projects/lib/tilt-card/ngwr-tilt-card.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport { DestroyRef, Directive, ElementRef, NgZone, PLATFORM_ID, effect, inject, input } from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\n\n/**\n * 3D mouse-tilt on hover. Adds `perspective(…) rotateX(…) rotateY(…)` to\n * the host's transform tracking the cursor's position over it.\n *\n * @example\n * ```html\n * <div class=\"card\" wrTilt>…</div>\n * <div class=\"card\" wrTilt maxTilt=\"20\" [glare]=\"true\">…</div>\n * ```\n */\n@Directive({\n selector: '[wrTilt]',\n host: { '(pointermove)': 'onMove($event)', '(pointerleave)': 'onLeave()' },\n})\nexport class WrTilt {\n /** Maximum tilt in degrees. @default 12 */\n readonly maxTilt = input(12, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 12)) });\n\n /** CSS perspective in pixels. @default 800 */\n readonly perspective = input(800, {\n transform: (v: unknown): number => Math.max(100, coerceNumberProperty(v, 800)),\n });\n\n /** Scale applied while hovered. @default 1.03 */\n readonly scale = input(1.03, { transform: (v: unknown): number => Math.max(1, coerceNumberProperty(v, 1.03)) });\n\n /** Add a moving glare highlight overlay. @default false */\n readonly glare = input(false, { transform: coerceBooleanProperty });\n\n private readonly el = 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 glareEl: HTMLElement | null = null;\n\n constructor() {\n const host = this.el.nativeElement;\n host.style.transformStyle = 'preserve-3d';\n host.style.transition = 'transform 0.15s ease-out';\n\n // Signal inputs are not bound yet while the constructor runs, so reading\n // `glare()` here would always see the default and the overlay would never\n // appear. Track it instead, so toggling the input at runtime works too.\n // Only in the browser: `installGlare` measures with `getComputedStyle`,\n // and the server has no layout to measure.\n if (this.isBrowser) {\n effect(() => (this.glare() ? this.installGlare() : this.removeGlare()));\n }\n\n this.destroyRef.onDestroy(() => {\n host.style.transform = '';\n this.removeGlare();\n });\n }\n\n /** @internal */\n protected onMove(event: PointerEvent): void {\n // Reduced motion: no pointer-tracking tilt — the card stays flat.\n if (this.platform.prefersReducedMotion()) return;\n this.zone.runOutsideAngular(() => {\n const host = this.el.nativeElement;\n const rect = host.getBoundingClientRect();\n const px = (event.clientX - rect.left) / rect.width;\n const py = (event.clientY - rect.top) / rect.height;\n const rx = (0.5 - py) * this.maxTilt();\n const ry = (px - 0.5) * this.maxTilt();\n host.style.transform = `perspective(${this.perspective()}px) rotateX(${rx}deg) rotateY(${ry}deg) scale(${this.scale()})`;\n if (this.glare()) {\n host.style.setProperty('--wr-tilt-glare-x', `${px * 100}%`);\n host.style.setProperty('--wr-tilt-glare-y', `${py * 100}%`);\n }\n });\n }\n\n /** @internal */\n protected onLeave(): void {\n this.el.nativeElement.style.transform = '';\n }\n\n private installGlare(): void {\n if (this.glareEl) return;\n const host = this.el.nativeElement;\n if (getComputedStyle(host).position === 'static') host.style.position = 'relative';\n host.style.overflow = 'hidden';\n const glare = host.ownerDocument.createElement('span');\n glare.className = 'wr-tilt-glare';\n glare.setAttribute('aria-hidden', 'true');\n host.appendChild(glare);\n this.glareEl = glare;\n }\n\n private removeGlare(): void {\n this.glareEl?.remove();\n this.glareEl = null;\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 { Component, ViewEncapsulation } from '@angular/core';\n\nimport { WrTilt } from './tilt';\n\n/**\n * Card wrapper that applies the `[wrTilt]` directive out-of-the-box,\n * plus a small set of card-style defaults (radius, padding, surface).\n * Pass any inputs through — they're forwarded to the hosted directive.\n *\n * For arbitrary elements that just need the tilt behaviour, use the\n * `[wrTilt]` directive directly:\n *\n * ```html\n * <img wrTilt [maxTilt]=\"10\" src=\"…\" />\n * ```\n *\n * @example\n * ```html\n * <wr-tilt-card>\n * <h3>Hello</h3>\n * <p>Move the cursor across me.</p>\n * </wr-tilt-card>\n *\n * <wr-tilt-card [maxTilt]=\"20\" [glare]=\"true\">…</wr-tilt-card>\n * ```\n */\n@Component({\n selector: 'wr-tilt-card',\n template: '<ng-content />',\n styleUrl: './tilt-card.scss',\n encapsulation: ViewEncapsulation.None,\n hostDirectives: [\n {\n directive: WrTilt,\n inputs: ['maxTilt', 'perspective', 'scale', 'glare'],\n },\n ],\n host: { class: 'wr-tilt-card' },\n})\nexport class WrTiltCard {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-tilt-card.mjs","sources":["../../../projects/lib/tilt-card/tilt.ts","../../../projects/lib/tilt-card/tilt-card.ts","../../../projects/lib/tilt-card/ngwr-tilt-card.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty, coerceNumberProperty } from '@angular/cdk/coercion';\nimport { isPlatformBrowser } from '@angular/common';\nimport { DestroyRef, Directive, ElementRef, NgZone, PLATFORM_ID, effect, inject, input } from '@angular/core';\n\nimport { WrPlatform } from 'ngwr/platform';\n\n/**\n * 3D mouse-tilt on hover. Adds `perspective(…) rotateX(…) rotateY(…)` to\n * the host's transform tracking the cursor's position over it.\n *\n * @example\n * ```html\n * <div class=\"card\" wrTilt>…</div>\n * <div class=\"card\" wrTilt maxTilt=\"20\" [glare]=\"true\">…</div>\n * ```\n */\n@Directive({\n selector: '[wrTilt]',\n host: { '(pointermove)': 'onMove($event)', '(pointerleave)': 'onLeave()' },\n})\nexport class WrTilt {\n /** Maximum tilt in degrees. @default 12 */\n readonly maxTilt = input(12, { transform: (v: unknown): number => Math.max(0, coerceNumberProperty(v, 12)) });\n\n /** CSS perspective in pixels. @default 800 */\n readonly perspective = input(800, {\n transform: (v: unknown): number => Math.max(100, coerceNumberProperty(v, 800)),\n });\n\n /** Scale applied while hovered. @default 1.03 */\n readonly scale = input(1.03, { transform: (v: unknown): number => Math.max(1, coerceNumberProperty(v, 1.03)) });\n\n /** Add a moving glare highlight overlay. @default false */\n readonly glare = input(false, { transform: coerceBooleanProperty });\n\n private readonly el = 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 glareEl: HTMLElement | null = null;\n private priorOverflow = '';\n private priorPosition = '';\n\n constructor() {\n const host = this.el.nativeElement;\n host.style.transformStyle = 'preserve-3d';\n host.style.transition = 'transform 0.15s ease-out';\n\n // Signal inputs are not bound yet while the constructor runs, so reading\n // `glare()` here would always see the default and the overlay would never\n // appear. Track it instead, so toggling the input at runtime works too.\n // Only in the browser: `installGlare` measures with `getComputedStyle`,\n // and the server has no layout to measure.\n if (this.isBrowser) {\n effect(() => (this.glare() ? this.installGlare() : this.removeGlare()));\n }\n\n this.destroyRef.onDestroy(() => {\n host.style.transform = '';\n this.removeGlare();\n });\n }\n\n /** @internal */\n protected onMove(event: PointerEvent): void {\n // Reduced motion: no pointer-tracking tilt — the card stays flat.\n if (this.platform.prefersReducedMotion()) return;\n this.zone.runOutsideAngular(() => {\n const host = this.el.nativeElement;\n const rect = host.getBoundingClientRect();\n const px = (event.clientX - rect.left) / rect.width;\n const py = (event.clientY - rect.top) / rect.height;\n const rx = (0.5 - py) * this.maxTilt();\n const ry = (px - 0.5) * this.maxTilt();\n host.style.transform = `perspective(${this.perspective()}px) rotateX(${rx}deg) rotateY(${ry}deg) scale(${this.scale()})`;\n if (this.glare()) {\n host.style.setProperty('--wr-tilt-glare-x', `${px * 100}%`);\n host.style.setProperty('--wr-tilt-glare-y', `${py * 100}%`);\n }\n });\n }\n\n /** @internal */\n protected onLeave(): void {\n this.el.nativeElement.style.transform = '';\n }\n\n private installGlare(): void {\n if (this.glareEl) return;\n const host = this.el.nativeElement;\n\n // Remembered, not assumed: `overflow` is written unconditionally, so it can be\n // clobbering an author's own inline value, and a blanket `removeProperty` on the\n // way out would delete it. Capturing the empty string is exactly what restores an\n // element that had none — which is the common case, and the one that used to keep\n // `overflow: hidden` for good after `[glare]` was turned back off.\n this.priorOverflow = host.style.overflow;\n this.priorPosition = host.style.position;\n\n if (getComputedStyle(host).position === 'static') host.style.position = 'relative';\n host.style.overflow = 'hidden';\n const glare = host.ownerDocument.createElement('span');\n glare.className = 'wr-tilt-glare';\n glare.setAttribute('aria-hidden', 'true');\n host.appendChild(glare);\n this.glareEl = glare;\n }\n\n private removeGlare(): void {\n if (!this.glareEl) return;\n\n const host = this.el.nativeElement;\n this.glareEl.remove();\n this.glareEl = null;\n host.style.overflow = this.priorOverflow;\n host.style.position = this.priorPosition;\n // Nothing reads these once the overlay is gone; leaving them behind only makes a\n // destroyed directive look like it is still tracking a pointer.\n host.style.removeProperty('--wr-tilt-glare-x');\n host.style.removeProperty('--wr-tilt-glare-y');\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 { Component, ViewEncapsulation } from '@angular/core';\n\nimport { WrTilt } from './tilt';\n\n/**\n * Card wrapper that applies the `[wrTilt]` directive out-of-the-box,\n * plus a small set of card-style defaults (radius, padding, surface).\n * Pass any inputs through — they're forwarded to the hosted directive.\n *\n * For arbitrary elements that just need the tilt behaviour, use the\n * `[wrTilt]` directive directly:\n *\n * ```html\n * <img wrTilt [maxTilt]=\"10\" src=\"…\" />\n * ```\n *\n * @example\n * ```html\n * <wr-tilt-card>\n * <h3>Hello</h3>\n * <p>Move the cursor across me.</p>\n * </wr-tilt-card>\n *\n * <wr-tilt-card [maxTilt]=\"20\" [glare]=\"true\">…</wr-tilt-card>\n * ```\n */\n@Component({\n selector: 'wr-tilt-card',\n template: '<ng-content />',\n styleUrl: './tilt-card.scss',\n encapsulation: ViewEncapsulation.None,\n hostDirectives: [\n {\n directive: WrTilt,\n inputs: ['maxTilt', 'perspective', 'scale', 'glare'],\n },\n ],\n host: { class: 'wr-tilt-card' },\n})\nexport class WrTiltCard {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/fesm2022/ngwr-toast.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { ComponentPortal } from '@angular/cdk/portal';
|
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
3
|
import { input, output, signal, computed, inject, ElementRef, ViewEncapsulation, Component, DestroyRef, InjectionToken, Service, makeEnvironmentProviders } from '@angular/core';
|
|
4
4
|
import { WR_OVERLAY } from 'ngwr/overlay';
|
|
5
|
+
import { useI18nText } from 'ngwr/i18n';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @license
|
|
@@ -31,6 +32,10 @@ class WrToastItem {
|
|
|
31
32
|
/** Auto-dismiss duration in ms — used to scale the progress bar. `0` hides it. */
|
|
32
33
|
duration = input(0, /* @ts-ignore */
|
|
33
34
|
...(ngDevMode ? [{ debugName: "duration" }] : /* istanbul ignore next */ []));
|
|
35
|
+
/**
|
|
36
|
+
* Already resolved by the host — strings, not the config's nullable shape.
|
|
37
|
+
* The item renders them and never reaches for the catalog itself.
|
|
38
|
+
*/
|
|
34
39
|
labels = input.required(/* @ts-ignore */
|
|
35
40
|
...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
|
|
36
41
|
dismissed = output();
|
|
@@ -155,6 +160,28 @@ class WrToastHost {
|
|
|
155
160
|
/** Layout mode. @default 'stack' (Sonner-style; hover to fan out) */
|
|
156
161
|
mode = input('stack', /* @ts-ignore */
|
|
157
162
|
...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
163
|
+
/**
|
|
164
|
+
* Every string this host and its items render, resolved through the catalog.
|
|
165
|
+
*
|
|
166
|
+
* The config value WINS when the consumer sets one, and its default is now
|
|
167
|
+
* `null` — until v11 those defaults were English literals, so the four
|
|
168
|
+
* `toast.*` keys in the shipped catalogs were dead and a Russian app got an
|
|
169
|
+
* English close button. `region` had no key at all: the live region's name
|
|
170
|
+
* was the literal `'Notifications'` in a host binding.
|
|
171
|
+
*/
|
|
172
|
+
regionLabel = useI18nText(signal(null), 'toast.region', 'Notifications');
|
|
173
|
+
closeLabel = useI18nText(computed(() => this.config().labels.close), 'toast.close', 'Close');
|
|
174
|
+
copyLabel = useI18nText(computed(() => this.config().labels.copy), 'toast.copy', 'Copy');
|
|
175
|
+
copiedLabel = useI18nText(computed(() => this.config().labels.copied), 'toast.copied', 'Copied');
|
|
176
|
+
closeAllLabel = useI18nText(computed(() => this.config().labels.closeAll), 'toast.closeAll', 'Close all');
|
|
177
|
+
/** What `wr-toast-item` receives — resolved, so the item stays presentational. */
|
|
178
|
+
labels = computed(() => ({
|
|
179
|
+
close: this.closeLabel(),
|
|
180
|
+
copy: this.copyLabel(),
|
|
181
|
+
copied: this.copiedLabel(),
|
|
182
|
+
closeAll: this.closeAllLabel(),
|
|
183
|
+
}), /* @ts-ignore */
|
|
184
|
+
...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
|
|
158
185
|
/** @internal — hovering or focusing the stack toggles this in stack mode. */
|
|
159
186
|
expanded = signal(false, /* @ts-ignore */
|
|
160
187
|
...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
@@ -257,19 +284,19 @@ class WrToastHost {
|
|
|
257
284
|
return t.showCopy ?? this.config().showCopy;
|
|
258
285
|
}
|
|
259
286
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrToastHost, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
260
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: WrToastHost, isStandalone: true, selector: "wr-toast-host", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismissed: "dismissed", pauseRequested: "pauseRequested", resumeRequested: "resumeRequested", dismissAllRequested: "dismissAllRequested" }, host: { attributes: { "role": "region"
|
|
287
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.1", type: WrToastHost, isStandalone: true, selector: "wr-toast-host", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismissed: "dismissed", pauseRequested: "pauseRequested", resumeRequested: "resumeRequested", dismissAllRequested: "dismissAllRequested" }, host: { attributes: { "role": "region" }, listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()", "focusin": "onFocusIn()", "focusout": "onFocusOut()" }, properties: { "class": "classes()", "attr.aria-label": "regionLabel()" } }, ngImport: i0, template: "@if (closeAllVisible()) {\n <button type=\"button\" class=\"wr-toast-host__close-all\" (click)=\"dismissAllRequested.emit()\">\n {{ closeAllLabel() }} ({{ totalCount() }})\n </button>\n}\n\n@for (toast of toasts(); track toast.id) {\n <wr-toast\n [style.--wr-toast-stack-index]=\"stackIndex($index)\"\n [type]=\"toast.type ?? 'info'\"\n [title]=\"toast.title ?? null\"\n [message]=\"toast.message\"\n [dismissible]=\"toast.dismissible ?? true\"\n [showProgress]=\"showProgressFor(toast)\"\n [showCopy]=\"showCopyFor(toast)\"\n [duration]=\"toast.resolvedDuration\"\n [labels]=\"labels()\"\n (dismissed)=\"dismissed.emit(toast.id)\"\n (pauseRequested)=\"pauseRequested.emit(toast.id)\"\n (resumeRequested)=\"resumeRequested.emit(toast.id)\"\n />\n}\n", dependencies: [{ kind: "component", type: WrToastItem, selector: "wr-toast", inputs: ["type", "title", "message", "dismissible", "showProgress", "showCopy", "duration", "labels"], outputs: ["dismissed", "pauseRequested", "resumeRequested"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
261
288
|
}
|
|
262
289
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.1", ngImport: i0, type: WrToastHost, decorators: [{
|
|
263
290
|
type: Component,
|
|
264
291
|
args: [{ selector: 'wr-toast-host', encapsulation: ViewEncapsulation.None, host: {
|
|
265
292
|
'[class]': 'classes()',
|
|
266
293
|
role: 'region',
|
|
267
|
-
'aria-label': '
|
|
294
|
+
'[attr.aria-label]': 'regionLabel()',
|
|
268
295
|
'(mouseenter)': 'onMouseEnter()',
|
|
269
296
|
'(mouseleave)': 'onMouseLeave()',
|
|
270
297
|
'(focusin)': 'onFocusIn()',
|
|
271
298
|
'(focusout)': 'onFocusOut()',
|
|
272
|
-
}, imports: [WrToastItem], template: "@if (closeAllVisible()) {\n <button type=\"button\" class=\"wr-toast-host__close-all\" (click)=\"dismissAllRequested.emit()\">\n {{
|
|
299
|
+
}, imports: [WrToastItem], template: "@if (closeAllVisible()) {\n <button type=\"button\" class=\"wr-toast-host__close-all\" (click)=\"dismissAllRequested.emit()\">\n {{ closeAllLabel() }} ({{ totalCount() }})\n </button>\n}\n\n@for (toast of toasts(); track toast.id) {\n <wr-toast\n [style.--wr-toast-stack-index]=\"stackIndex($index)\"\n [type]=\"toast.type ?? 'info'\"\n [title]=\"toast.title ?? null\"\n [message]=\"toast.message\"\n [dismissible]=\"toast.dismissible ?? true\"\n [showProgress]=\"showProgressFor(toast)\"\n [showCopy]=\"showCopyFor(toast)\"\n [duration]=\"toast.resolvedDuration\"\n [labels]=\"labels()\"\n (dismissed)=\"dismissed.emit(toast.id)\"\n (pauseRequested)=\"pauseRequested.emit(toast.id)\"\n (resumeRequested)=\"resumeRequested.emit(toast.id)\"\n />\n}\n" }]
|
|
273
300
|
}], ctorParameters: () => [], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], dismissed: [{ type: i0.Output, args: ["dismissed"] }], pauseRequested: [{ type: i0.Output, args: ["pauseRequested"] }], resumeRequested: [{ type: i0.Output, args: ["resumeRequested"] }], dismissAllRequested: [{ type: i0.Output, args: ["dismissAllRequested"] }] } });
|
|
274
301
|
|
|
275
302
|
/**
|
|
@@ -318,11 +345,13 @@ const DEFAULT_TOAST_CONFIG = {
|
|
|
318
345
|
showCloseAll: true,
|
|
319
346
|
closeAllThreshold: 2,
|
|
320
347
|
maxStack: 5,
|
|
348
|
+
// Null, not English: the host resolves each one through the i18n catalog and
|
|
349
|
+
// falls back to English only if nothing is registered. See `WrToastConfig`.
|
|
321
350
|
labels: {
|
|
322
|
-
close:
|
|
323
|
-
copy:
|
|
324
|
-
copied:
|
|
325
|
-
closeAll:
|
|
351
|
+
close: null,
|
|
352
|
+
copy: null,
|
|
353
|
+
copied: null,
|
|
354
|
+
closeAll: null,
|
|
326
355
|
},
|
|
327
356
|
};
|
|
328
357
|
|