ngwr 14.0.0 → 14.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -8
- package/aurora/aurora.scss +1 -1
- package/aurora/styles/_dark.scss +21 -0
- package/aurora/styles/_index.scss +11 -30
- package/aurora/styles/_rules.scss +25 -0
- package/border-glow/styles/_dark.scss +46 -0
- package/border-glow/styles/_index.scss +5 -0
- package/border-glow/styles/_rules.scss +0 -31
- package/button/styles/_index.scss +16 -0
- package/event-calendar/styles/_index.scss +15 -0
- package/fesm2022/ngwr-action-sheet.mjs +3 -3
- package/fesm2022/ngwr-affix.mjs +3 -3
- package/fesm2022/ngwr-alert.mjs +3 -3
- package/fesm2022/ngwr-anchor.mjs +3 -3
- package/fesm2022/ngwr-aurora.mjs +4 -4
- package/fesm2022/ngwr-aurora.mjs.map +1 -1
- 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-bar-chart.mjs +3 -3
- package/fesm2022/ngwr-blur-text.mjs +3 -3
- package/fesm2022/ngwr-border-glow.mjs +4 -4
- package/fesm2022/ngwr-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-breadcrumbs.mjs +7 -7
- package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
- package/fesm2022/ngwr-burger.mjs +3 -3
- package/fesm2022/ngwr-button.mjs +86 -8
- package/fesm2022/ngwr-button.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-heatmap.mjs +3 -3
- 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 +4 -4
- package/fesm2022/ngwr-cascader.mjs.map +1 -1
- package/fesm2022/ngwr-checkbox.mjs +6 -6
- package/fesm2022/ngwr-circular-text.mjs +3 -3
- package/fesm2022/ngwr-click-spark.mjs +3 -3
- 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-confetti.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-adapters-fns.mjs +3 -3
- package/fesm2022/ngwr-date-adapters-luxon.mjs +3 -3
- package/fesm2022/ngwr-date-picker.mjs +18 -18
- package/fesm2022/ngwr-date.mjs +3 -3
- package/fesm2022/ngwr-decrypt-text.mjs +3 -3
- 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-donut-chart.mjs +3 -3
- package/fesm2022/ngwr-drag-drop.mjs +7 -7
- package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +18 -18
- package/fesm2022/ngwr-dropdown.mjs +28 -12
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-empty.mjs +3 -3
- package/fesm2022/ngwr-event-calendar.mjs +6 -6
- package/fesm2022/ngwr-falling-text.mjs +3 -3
- package/fesm2022/ngwr-file-upload.mjs +3 -3
- package/fesm2022/ngwr-form-testing.mjs +13 -7
- package/fesm2022/ngwr-form-testing.mjs.map +1 -1
- package/fesm2022/ngwr-form.mjs +44 -25
- package/fesm2022/ngwr-form.mjs.map +1 -1
- package/fesm2022/ngwr-fuzzy-text.mjs +3 -3
- package/fesm2022/ngwr-gauge.mjs +3 -3
- package/fesm2022/ngwr-glitch-text.mjs +3 -3
- package/fesm2022/ngwr-gradient-text.mjs +3 -3
- 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 +24 -20
- package/fesm2022/ngwr-input.mjs.map +1 -1
- 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 +4 -4
- package/fesm2022/ngwr-lightbox.mjs.map +1 -1
- package/fesm2022/ngwr-line-chart.mjs +3 -3
- package/fesm2022/ngwr-list.mjs +7 -7
- package/fesm2022/ngwr-list.mjs.map +1 -1
- package/fesm2022/ngwr-loading-bar.mjs +6 -6
- package/fesm2022/ngwr-markdown.mjs +6 -6
- package/fesm2022/ngwr-marquee.mjs +3 -3
- package/fesm2022/ngwr-media.mjs +3 -3
- package/fesm2022/ngwr-mention.mjs +6 -6
- package/fesm2022/ngwr-meta.mjs +6 -6
- package/fesm2022/ngwr-meter-group.mjs +3 -3
- 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 +9 -9
- package/fesm2022/ngwr-popconfirm.mjs +6 -6
- package/fesm2022/ngwr-popover.mjs +6 -6
- package/fesm2022/ngwr-progress.mjs +3 -3
- package/fesm2022/ngwr-pull-to-refresh.mjs +3 -3
- package/fesm2022/ngwr-qr.mjs +3 -3
- package/fesm2022/ngwr-radio.mjs +6 -6
- package/fesm2022/ngwr-rating.mjs +48 -22
- package/fesm2022/ngwr-rating.mjs.map +1 -1
- package/fesm2022/ngwr-result.mjs +12 -12
- package/fesm2022/ngwr-rotating-text.mjs +3 -3
- package/fesm2022/ngwr-scroll.mjs +3 -3
- package/fesm2022/ngwr-segmented.mjs +12 -8
- package/fesm2022/ngwr-segmented.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +125 -26
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text.mjs +7 -7
- package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
- package/fesm2022/ngwr-sidebar.mjs +3 -3
- package/fesm2022/ngwr-skeleton.mjs +3 -3
- package/fesm2022/ngwr-slider.mjs +12 -8
- package/fesm2022/ngwr-slider.mjs.map +1 -1
- package/fesm2022/ngwr-sparkline.mjs +3 -3
- package/fesm2022/ngwr-speed-dial.mjs +3 -3
- package/fesm2022/ngwr-spinner.mjs +3 -3
- package/fesm2022/ngwr-splash-cursor.mjs +3 -3
- package/fesm2022/ngwr-split-text.mjs +3 -3
- package/fesm2022/ngwr-splitter.mjs +3 -3
- package/fesm2022/ngwr-spotlight-card.mjs +7 -7
- package/fesm2022/ngwr-spotlight-card.mjs.map +1 -1
- package/fesm2022/ngwr-squircle.mjs +6 -6
- package/fesm2022/ngwr-star-border.mjs +4 -4
- package/fesm2022/ngwr-star-border.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +9 -9
- package/fesm2022/ngwr-stepper.mjs +6 -6
- package/fesm2022/ngwr-storage.mjs +11 -3
- package/fesm2022/ngwr-storage.mjs.map +1 -1
- 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 +26 -5
- package/fesm2022/ngwr-textarea.mjs.map +1 -1
- package/fesm2022/ngwr-theme.mjs +90 -4
- package/fesm2022/ngwr-theme.mjs.map +1 -1
- package/fesm2022/ngwr-tilt-card.mjs +7 -7
- package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
- package/fesm2022/ngwr-timeline.mjs +6 -6
- package/fesm2022/ngwr-toast.mjs +116 -11
- package/fesm2022/ngwr-toast.mjs.map +1 -1
- 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 +18 -11
- package/fesm2022/ngwr-tree.mjs.map +1 -1
- package/fesm2022/ngwr-typewriter.mjs +3 -3
- package/fesm2022/ngwr-typography.mjs +3 -3
- 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-waves.mjs +4 -4
- package/fesm2022/ngwr-waves.mjs.map +1 -1
- package/fesm2022/ngwr-window.mjs +12 -12
- package/llms-full.txt +9 -9
- package/llms.txt +82 -11
- package/package.json +1 -1
- package/shiny-text/styles/_dark.scss +20 -0
- package/shiny-text/styles/_index.scss +5 -0
- package/shiny-text/styles/_rules.scss +0 -4
- package/spotlight-card/styles/_dark.scss +21 -0
- package/spotlight-card/styles/_index.scss +5 -0
- package/spotlight-card/styles/_rules.scss +0 -6
- package/star-border/styles/_dark.scss +21 -0
- package/star-border/styles/_index.scss +5 -0
- package/star-border/styles/_rules.scss +0 -5
- package/theme/styles/_colors.scss +102 -0
- package/theme/styles/_focus.scss +31 -1
- package/tilt-card/styles/_dark.scss +21 -0
- package/tilt-card/styles/_index.scss +5 -0
- package/tilt-card/styles/_rules.scss +0 -5
- package/types/ngwr-button.d.ts +43 -0
- package/types/ngwr-dropdown.d.ts +17 -1
- package/types/ngwr-form-testing.d.ts +13 -7
- package/types/ngwr-form.d.ts +47 -12
- package/types/ngwr-input.d.ts +2 -1
- package/types/ngwr-rating.d.ts +25 -4
- package/types/ngwr-segmented.d.ts +1 -0
- package/types/ngwr-select.d.ts +45 -9
- package/types/ngwr-slider.d.ts +1 -0
- package/types/ngwr-storage.d.ts +39 -1
- package/types/ngwr-textarea.d.ts +20 -0
- package/types/ngwr-theme.d.ts +92 -2
- package/types/ngwr-toast.d.ts +53 -0
- package/types/ngwr-tree.d.ts +3 -2
- package/types/ngwr-version.d.ts +1 -1
- package/waves/styles/_dark.scss +19 -0
- package/waves/styles/_index.scss +5 -0
- package/waves/styles/_rules.scss +0 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-dropdown.mjs","sources":["../../../projects/lib/dropdown/interfaces/dropdown-position.ts","../../../projects/lib/dropdown/dropdown.ts","../../../projects/lib/dropdown/dropdown-menu.ts","../../../projects/lib/dropdown/dropdown-item.ts","../../../projects/lib/dropdown/dropdown-item.html","../../../projects/lib/dropdown/ngwr-dropdown.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 type { ConnectedPosition } from '@angular/cdk/overlay';\n\n/**\n * Where the dropdown menu opens, relative to the trigger.\n */\nexport type WrDropdownPosition =\n 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'right';\n\n/**\n * Maps friendly names to CDK ConnectedPosition fallback lists.\n *\n * @internal\n */\nexport const WR_DROPDOWN_POSITIONS: Record<WrDropdownPosition, ConnectedPosition[]> = {\n top: [{ originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom' }],\n 'top-start': [{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' }],\n 'top-end': [{ originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom' }],\n bottom: [{ originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top' }],\n 'bottom-start': [{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' }],\n 'bottom-end': [{ originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top' }],\n left: [{ originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center' }],\n right: [{ originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center' }],\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 { type BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { type OverlayRef, ScrollStrategyOptions } from '@angular/cdk/overlay';\nimport { TemplatePortal } from '@angular/cdk/portal';\nimport {\n DestroyRef,\n Directive,\n ElementRef,\n ViewContainerRef,\n effect,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { WR_OVERLAY, WR_RESPONSIVE_OVERLAYS, WrOutsideClick, wrPresentAsSheet } from 'ngwr/overlay';\n\nimport type { WrDropdownMenu } from './dropdown-menu';\nimport { WR_DROPDOWN_POSITIONS, type WrDropdownPosition, type WrDropdownTrigger } from './interfaces';\n\n/**\n * Attach to any element to open a `<wr-dropdown-menu>` as a CDK overlay.\n *\n * @example\n * ```html\n * <button [wrDropdown]=\"menu\" position=\"bottom-start\" trigger=\"click\">\n * Actions\n * </button>\n *\n * <wr-dropdown-menu #menu>\n * <wr-dropdown-item icon=\"copy\" (click)=\"copy()\">Copy</wr-dropdown-item>\n * <wr-dropdown-item icon=\"trash\" (click)=\"remove()\">Delete</wr-dropdown-item>\n * </wr-dropdown-menu>\n * ```\n *\n * @see https://ngwr.dev/reference/components/dropdown\n */\nlet triggerUid = 0;\n\n@Directive({\n selector: '[wrDropdown]',\n exportAs: 'wrDropdown',\n host: {\n class: 'wr-dropdown-trigger',\n '[attr.id]': 'triggerId',\n '[attr.aria-haspopup]': '\"menu\"',\n '[attr.aria-expanded]': 'isOpen()',\n '[attr.aria-controls]': 'isOpen() ? menu().menuId() : null',\n '(click)': 'onClick($event)',\n '(keydown)': 'onKeydown($event)',\n '(mouseenter)': 'onMouseEnter()',\n '(mouseleave)': 'onMouseLeave($event)',\n },\n})\nexport class WrDropdown {\n /** Menu to open. Pass a `<wr-dropdown-menu>` template reference. */\n readonly menu = input.required<WrDropdownMenu>({ alias: 'wrDropdown' });\n\n /** How the menu opens. @default 'click' */\n readonly trigger = input<WrDropdownTrigger>('click');\n\n /** Where the menu anchors relative to the trigger. @default 'bottom-start' */\n readonly position = input<WrDropdownPosition>('bottom-start');\n\n /**\n * Present the menu as a full-width bottom-sheet on small viewports instead\n * of an anchored panel. `undefined` follows the app-wide\n * `provideWrResponsiveOverlays()` setting; `true`/`false` overrides it.\n * @default undefined\n */\n readonly responsive = input<boolean | undefined, BooleanInput>(undefined, {\n transform: (v: BooleanInput): boolean | undefined => (v == null ? undefined : coerceBooleanProperty(v)),\n });\n\n /** Fires after the menu opens. */\n readonly opened = output<void>();\n\n /** Fires after the menu closes. */\n readonly closed = output<void>();\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly overlay = inject(WR_OVERLAY);\n private readonly outsideClick = inject(WrOutsideClick);\n private readonly responsiveConfig = inject(WR_RESPONSIVE_OVERLAYS);\n private readonly vcr = inject(ViewContainerRef);\n private readonly scrollStrategies = inject(ScrollStrategyOptions);\n private readonly destroyRef = inject(DestroyRef);\n\n /** @internal Public so host bindings can read it. */\n readonly isOpen = signal(false);\n\n /**\n * Id the menu names itself after. The trigger is the CONSUMER's own element, so\n * an `id` they already put there wins: overwriting it broke `<label for>`,\n * `document.getElementById` and any `aria-labelledby` aimed at that button from\n * elsewhere in their app. The generated one is only a fallback so the menu has\n * something to reference.\n */\n // `.id` rather than `getAttribute`: it is always a string, and an empty one is no\n // id at all, so it falls through to the generated fallback.\n protected readonly triggerId = this.host.nativeElement.id || `wr-dropdown-trigger-${++triggerUid}`;\n\n private overlayRef: OverlayRef | null = null;\n\n constructor() {\n // The host view carrying `[wrDropdown]` can be destroyed while the menu is\n // open — a route change, or an `@if` around the trigger. The overlay lives\n // in the CDK container, not in that view, so nothing removes it unless we do.\n this.destroyRef.onDestroy(() => this.closeOverlay());\n\n effect(() => {\n if (this.isOpen()) {\n this.openOverlay();\n } else {\n this.closeOverlay();\n }\n });\n }\n\n /** Open the menu. */\n open(): void {\n this.isOpen.set(true);\n }\n\n /** Close the menu. */\n close(): void {\n this.isOpen.set(false);\n }\n\n /** Toggle the menu. */\n toggle(): void {\n this.isOpen.update(v => !v);\n }\n\n // Host listeners\n\n /** @internal */\n /**\n * Whether the pointer, rather than the keyboard, opened the current menu.\n *\n * A hover-opened menu must not take focus — the APG shows a menu on hover and\n * leaves the caret where it was — while a keyboard-opened one must. The\n * trigger MODE cannot answer that on its own: a `trigger=\"hover\"` menu is\n * still openable with Enter, and that open should focus the first item.\n */\n private openedByPointer = false;\n\n protected onClick(event: MouseEvent): void {\n if (this.trigger() !== 'click') return;\n event.stopPropagation();\n this.openedByPointer = false;\n this.toggle();\n }\n\n /** @internal */\n protected onMouseEnter(): void {\n if (this.trigger() !== 'hover') return;\n // A POINTER opened this, so the menu must not take the keyboard with it —\n // see `openedByPointer`.\n this.openedByPointer = true;\n this.isOpen.set(true);\n }\n\n /** @internal */\n protected onMouseLeave(event: MouseEvent): void {\n if (this.trigger() !== 'hover') return;\n const related = event.relatedTarget as Node | null;\n if (related && this.overlayRef?.overlayElement.contains(related)) return;\n this.isOpen.set(false);\n }\n\n // Overlay management\n\n private openOverlay(): void {\n if (this.overlayRef) return;\n\n // On small viewports (when opted in) detach from the trigger and present\n // the menu as a full-width slide-up sheet pinned to the bottom edge.\n const asSheet = wrPresentAsSheet(this.responsive(), this.responsiveConfig);\n\n const positionStrategy = asSheet\n ? this.overlay.position().global().centerHorizontally().bottom('0')\n : this.overlay\n .position()\n .flexibleConnectedTo(this.host)\n .withPositions(WR_DROPDOWN_POSITIONS[this.position()])\n .withPush(true);\n\n this.overlayRef = this.overlay.create({\n positionStrategy,\n scrollStrategy: asSheet ? this.scrollStrategies.block() : this.scrollStrategies.reposition(),\n width: asSheet ? '100%' : undefined,\n hasBackdrop: asSheet,\n backdropClass: asSheet ? 'wr-overlay-backdrop' : undefined,\n panelClass: asSheet\n ? ['wr-dropdown-overlay', 'wr-overlay-sheet']\n : ['wr-dropdown-overlay', `wr-dropdown-overlay--${this.position()}`],\n });\n\n if (asSheet) {\n this.overlayRef\n .backdropClick()\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(() => this.isOpen.set(false));\n }\n\n // Wire trigger id into menu so it can render aria-labelledby.\n this.menu().triggerId.set(this.triggerId);\n\n const portal = new TemplatePortal(this.menu().contentTpl(), this.vcr);\n this.overlayRef.attach(portal);\n\n // Focus the first menu item after the menu renders — unless a POINTER\n // opened it. This used to be unconditional, so merely sweeping the cursor\n // over a `trigger=\"hover\"` menu yanked the caret out of whatever the user\n // was typing; the APG's own rule is that hover shows a menu and does not\n // move focus into it. A keyboard open (Enter / Space / ArrowDown) still\n // focuses the first item, on a hover trigger too.\n if (!this.openedByPointer) queueMicrotask(() => this.focusItemAt(0));\n\n this.outsideClick\n .outsidePointerEvents(this.overlayRef)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(event => {\n if (this.host.nativeElement.contains(event.target as Node)) return;\n this.isOpen.set(false);\n });\n\n this.overlayRef\n .keydownEvents()\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(event => {\n this.handleMenuKeydown(event);\n });\n\n if (this.trigger() === 'hover') {\n this.overlayRef.overlayElement.addEventListener('mouseleave', this.onOverlayMouseLeave);\n }\n\n this.opened.emit();\n }\n\n private closeOverlay(): void {\n if (!this.overlayRef) return;\n if (this.trigger() === 'hover') {\n this.overlayRef.overlayElement.removeEventListener('mouseleave', this.onOverlayMouseLeave);\n }\n // Take the focus back before the pane goes, or it lands on `<body>` and the\n // next Tab starts from the top of the document. Only when focus is actually\n // inside the menu: closing a menu nobody was in must not steal the caret\n // from wherever the user has since moved.\n const active = document.activeElement;\n const focusWasInside = active instanceof Node && this.overlayRef.overlayElement.contains(active);\n this.overlayRef.dispose();\n this.overlayRef = null;\n if (focusWasInside) this.host.nativeElement.focus();\n this.closed.emit();\n }\n\n // Keyboard handling\n\n /** @internal Trigger keydown — ArrowDown/Up/Enter/Space open the menu. */\n protected onKeydown(event: KeyboardEvent): void {\n if (this.isOpen()) return;\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n // Opened from the keyboard — including on a `hover` trigger, which stays\n // keyboard-operable — so the first item SHOULD take focus.\n this.openedByPointer = false;\n this.open();\n }\n }\n\n private handleMenuKeydown(event: KeyboardEvent): void {\n const items = this.getItems();\n if (items.length === 0 && event.key !== 'Escape' && event.key !== 'Tab') return;\n const current = document.activeElement as HTMLElement | null;\n const idx = current ? items.indexOf(current) : -1;\n\n // CDK's keyboard dispatcher feeds this overlay EVERY document keydown, not\n // just the ones typed into it — and a `trigger=\"hover\"` menu is open with\n // the caret deliberately left wherever the user was (see `openedByPointer`).\n // So ownership is checked here rather than assumed: without it, ArrowDown /\n // ArrowUp / Home / End pressed in an unrelated field were `preventDefault`ed\n // and yanked focus into the menu. The trigger counts as ours — a hover menu\n // that the user has tabbed back to should still open to the keyboard.\n const typedIn = event.target;\n const ours =\n typedIn instanceof Node &&\n (this.host.nativeElement.contains(typedIn) || (this.overlayRef?.overlayElement.contains(typedIn) ?? false));\n // Escape (and Tab) still close from anywhere — routing to the topmost\n // overlay regardless of focus is the dispatcher's whole job. Escape just\n // must not drag the caret onto the trigger from a field the user is in.\n if (!ours && event.key !== 'Escape' && event.key !== 'Tab') return;\n\n switch (event.key) {\n case 'Escape':\n event.preventDefault();\n this.isOpen.set(false);\n if (ours) this.host.nativeElement.focus();\n break;\n case 'Tab':\n // Let focus leave naturally; close the menu.\n this.isOpen.set(false);\n break;\n case 'ArrowDown':\n event.preventDefault();\n this.focusItemAt(idx < items.length - 1 ? idx + 1 : 0);\n break;\n case 'ArrowUp':\n event.preventDefault();\n this.focusItemAt(idx > 0 ? idx - 1 : items.length - 1);\n break;\n case 'Home':\n event.preventDefault();\n this.focusItemAt(0);\n break;\n case 'End':\n event.preventDefault();\n this.focusItemAt(items.length - 1);\n break;\n default:\n break;\n }\n }\n\n private getItems(): readonly HTMLElement[] {\n if (!this.overlayRef) return [];\n return Array.from(\n this.overlayRef.overlayElement.querySelectorAll<HTMLElement>('.wr-dropdown-item:not(.wr-dropdown-item--disabled)')\n );\n }\n\n private focusItemAt(index: number): void {\n const items = this.getItems();\n if (items.length === 0) return;\n const clamped = ((index % items.length) + items.length) % items.length;\n items[clamped]?.focus();\n }\n\n private readonly onOverlayMouseLeave = (event: MouseEvent): void => {\n const related = event.relatedTarget as Node | null;\n if (related && this.host.nativeElement.contains(related)) return;\n this.isOpen.set(false);\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, TemplateRef, ViewEncapsulation, signal, viewChild } from '@angular/core';\n\nlet uid = 0;\n\n/**\n * Holds the menu content rendered when a {@link WrDropdown} opens.\n *\n * The component itself doesn't render anywhere — the directive grabs its\n * internal `<ng-template>` and portals it into an overlay.\n *\n * @example\n * ```html\n * <button [wrDropdown]=\"menu\">Actions</button>\n * <wr-dropdown-menu #menu>\n * <wr-dropdown-item icon=\"copy\">Copy</wr-dropdown-item>\n * <wr-dropdown-item icon=\"trash\">Delete</wr-dropdown-item>\n * </wr-dropdown-menu>\n * ```\n *\n * @see https://ngwr.dev/reference/components/dropdown\n */\n@Component({\n selector: 'wr-dropdown-menu',\n template:\n '<ng-template><div class=\"wr-dropdown-menu\" role=\"menu\" [attr.id]=\"menuId()\" [attr.aria-labelledby]=\"triggerId() || null\"><ng-content /></div></ng-template>',\n exportAs: 'wrDropdownMenu',\n encapsulation: ViewEncapsulation.None,\n // A BOUND display rather than a static `style` attribute: Angular writes a\n // binding through `style.setProperty`, which no CSP governs, while a real\n // `style=\"…\"` attribute is refused under `style-src 'self'` — and this host\n // would then lay out an empty box in the middle of the content.\n host: { '[style.display]': \"'none'\" },\n})\nexport class WrDropdownMenu {\n /** The internal template the directive portals into the overlay. @internal */\n readonly contentTpl = viewChild.required(TemplateRef);\n\n /** Stable id used on the menu element and referenced from the trigger's `aria-controls`. */\n readonly menuId = signal(`wr-dropdown-menu-${++uid}`);\n\n /** Trigger element id, set by the directive for `aria-labelledby` wiring. */\n readonly triggerId = signal<string | null>(null);\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 { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ElementRef, ViewEncapsulation, computed, inject, input } from '@angular/core';\n\nimport { WrIcon, type WrIconName } from 'ngwr/icon';\n\n/**\n * Single row inside a `<wr-dropdown-menu>`.\n *\n * @example\n * ```html\n * <wr-dropdown-item icon=\"copy\" (click)=\"copy()\">Copy</wr-dropdown-item>\n * <wr-dropdown-item icon=\"trash\" disabled>Delete</wr-dropdown-item>\n * ```\n */\n@Component({\n selector: 'wr-dropdown-item',\n templateUrl: './dropdown-item.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n role: 'menuitem',\n '[class]': 'classes()',\n '[attr.tabindex]': '-1',\n '[attr.aria-disabled]': 'disabled() ? true : null',\n '(keydown.enter)': 'activate($event)',\n '(keydown.space)': 'activate($event)',\n },\n imports: [WrIcon],\n})\nexport class WrDropdownItem {\n /** Optional leading icon name. @default null */\n readonly icon = input<WrIconName | null>(null);\n\n /** Disable interaction (suppresses pointer + keyboard). @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n protected readonly classes = computed(() => {\n const parts = ['wr-dropdown-item'];\n if (this.disabled()) parts.push('wr-dropdown-item--disabled');\n return parts.join(' ');\n });\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /** @internal Keyboard activation — fires the same click consumers bind to. */\n protected activate(event: Event): void {\n if (this.disabled()) return;\n event.preventDefault();\n this.host.nativeElement.click();\n }\n}\n","@if (icon(); as name) {\n <wr-icon class=\"wr-dropdown-item__icon\" [name]=\"name\" />\n}\n<span class=\"wr-dropdown-item__label\">\n <ng-content />\n</span>\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-dropdown.mjs","sources":["../../../projects/lib/dropdown/interfaces/dropdown-position.ts","../../../projects/lib/dropdown/dropdown.ts","../../../projects/lib/dropdown/dropdown-menu.ts","../../../projects/lib/dropdown/dropdown-item.ts","../../../projects/lib/dropdown/dropdown-item.html","../../../projects/lib/dropdown/ngwr-dropdown.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 type { ConnectedPosition } from '@angular/cdk/overlay';\n\n/**\n * Where the dropdown menu opens, relative to the trigger.\n */\nexport type WrDropdownPosition =\n 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left' | 'right';\n\n/**\n * Maps friendly names to CDK ConnectedPosition fallback lists.\n *\n * @internal\n */\nexport const WR_DROPDOWN_POSITIONS: Record<WrDropdownPosition, ConnectedPosition[]> = {\n top: [{ originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom' }],\n 'top-start': [{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' }],\n 'top-end': [{ originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom' }],\n bottom: [{ originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top' }],\n 'bottom-start': [{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' }],\n 'bottom-end': [{ originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top' }],\n left: [{ originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center' }],\n right: [{ originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center' }],\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 { type BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { type OverlayRef, ScrollStrategyOptions } from '@angular/cdk/overlay';\nimport { TemplatePortal } from '@angular/cdk/portal';\nimport {\n DestroyRef,\n Directive,\n ElementRef,\n ViewContainerRef,\n effect,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { WR_OVERLAY, WR_RESPONSIVE_OVERLAYS, WrOutsideClick, wrPresentAsSheet } from 'ngwr/overlay';\n\nimport type { WrDropdownMenu } from './dropdown-menu';\nimport { WR_DROPDOWN_POSITIONS, type WrDropdownPosition, type WrDropdownTrigger } from './interfaces';\n\n/**\n * Attach to any element to open a `<wr-dropdown-menu>` as a CDK overlay.\n *\n * @example\n * ```html\n * <button [wrDropdown]=\"menu\" position=\"bottom-start\" trigger=\"click\">\n * Actions\n * </button>\n *\n * <wr-dropdown-menu #menu>\n * <wr-dropdown-item icon=\"copy\" (click)=\"copy()\">Copy</wr-dropdown-item>\n * <wr-dropdown-item icon=\"trash\" (click)=\"remove()\">Delete</wr-dropdown-item>\n * </wr-dropdown-menu>\n * ```\n *\n * @see https://ngwr.dev/reference/components/dropdown\n */\nlet triggerUid = 0;\n\n@Directive({\n selector: '[wrDropdown]',\n exportAs: 'wrDropdown',\n host: {\n class: 'wr-dropdown-trigger',\n '[attr.id]': 'triggerId',\n '[attr.aria-haspopup]': '\"menu\"',\n '[attr.aria-expanded]': 'isOpen()',\n '[attr.aria-controls]': 'isOpen() ? menu().menuId() : null',\n '(click)': 'onClick($event)',\n '(keydown)': 'onKeydown($event)',\n '(mouseenter)': 'onMouseEnter()',\n '(mouseleave)': 'onMouseLeave($event)',\n },\n})\nexport class WrDropdown {\n /** Menu to open. Pass a `<wr-dropdown-menu>` template reference. */\n readonly menu = input.required<WrDropdownMenu>({ alias: 'wrDropdown' });\n\n /** How the menu opens. @default 'click' */\n readonly trigger = input<WrDropdownTrigger>('click');\n\n /** Where the menu anchors relative to the trigger. @default 'bottom-start' */\n readonly position = input<WrDropdownPosition>('bottom-start');\n\n /**\n * Present the menu as a full-width bottom-sheet on small viewports instead\n * of an anchored panel. `undefined` follows the app-wide\n * `provideWrResponsiveOverlays()` setting; `true`/`false` overrides it.\n * @default undefined\n */\n readonly responsive = input<boolean | undefined, BooleanInput>(undefined, {\n transform: (v: BooleanInput): boolean | undefined => (v == null ? undefined : coerceBooleanProperty(v)),\n });\n\n /** Fires after the menu opens. */\n readonly opened = output<void>();\n\n /** Fires after the menu closes. */\n readonly closed = output<void>();\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n private readonly overlay = inject(WR_OVERLAY);\n private readonly outsideClick = inject(WrOutsideClick);\n private readonly responsiveConfig = inject(WR_RESPONSIVE_OVERLAYS);\n private readonly vcr = inject(ViewContainerRef);\n private readonly scrollStrategies = inject(ScrollStrategyOptions);\n private readonly destroyRef = inject(DestroyRef);\n\n /** @internal Public so host bindings can read it. */\n readonly isOpen = signal(false);\n\n /** The id used only when the consumer's element carries none of its own. */\n private readonly fallbackId = `wr-dropdown-trigger-${++triggerUid}`;\n\n /**\n * Id the menu names itself after. The trigger is the CONSUMER's own element, so\n * an `id` they already put there wins: overwriting it broke `<label for>`,\n * `document.getElementById` and any `aria-labelledby` aimed at that button from\n * elsewhere in their app. The generated one is only a fallback so the menu has\n * something to reference.\n *\n * A GETTER, read fresh by the host binding on every pass, rather than a field\n * resolved in the constructor. A static `id=\"…\"` is on the element before a\n * directive is instantiated and so was already honoured; a BOUND `[id]` is\n * not — it lands with the rest of the template's bindings, which run before\n * host bindings but after construction, so the constructor read an empty\n * string, fell through to the fallback, and the host binding then wrote that\n * fallback over the value the template had just set. Everything bound was\n * lost — `[id]`, `[attr.id]` and an interpolated `id=\"dd-{{ n }}\"` alike —\n * while the static form survived, which is the shape that makes it look like\n * a naming convention rather than a defect.\n *\n * `.id` rather than `getAttribute`: it is always a string, and an empty one is\n * no id at all, so it falls through to the fallback.\n */\n protected get triggerId(): string {\n return this.host.nativeElement.id || this.fallbackId;\n }\n\n private overlayRef: OverlayRef | null = null;\n\n constructor() {\n // The host view carrying `[wrDropdown]` can be destroyed while the menu is\n // open — a route change, or an `@if` around the trigger. The overlay lives\n // in the CDK container, not in that view, so nothing removes it unless we do.\n this.destroyRef.onDestroy(() => this.closeOverlay());\n\n effect(() => {\n if (this.isOpen()) {\n this.openOverlay();\n } else {\n this.closeOverlay();\n }\n });\n }\n\n /** Open the menu. */\n open(): void {\n this.isOpen.set(true);\n }\n\n /** Close the menu. */\n close(): void {\n this.isOpen.set(false);\n }\n\n /** Toggle the menu. */\n toggle(): void {\n this.isOpen.update(v => !v);\n }\n\n // Host listeners\n\n /** @internal */\n /**\n * Whether the pointer, rather than the keyboard, opened the current menu.\n *\n * A hover-opened menu must not take focus — the APG shows a menu on hover and\n * leaves the caret where it was — while a keyboard-opened one must. The\n * trigger MODE cannot answer that on its own: a `trigger=\"hover\"` menu is\n * still openable with Enter, and that open should focus the first item.\n */\n private openedByPointer = false;\n\n protected onClick(event: MouseEvent): void {\n if (this.trigger() !== 'click') return;\n event.stopPropagation();\n this.openedByPointer = false;\n this.toggle();\n }\n\n /** @internal */\n protected onMouseEnter(): void {\n if (this.trigger() !== 'hover') return;\n // A POINTER opened this, so the menu must not take the keyboard with it —\n // see `openedByPointer`.\n this.openedByPointer = true;\n this.isOpen.set(true);\n }\n\n /** @internal */\n protected onMouseLeave(event: MouseEvent): void {\n if (this.trigger() !== 'hover') return;\n const related = event.relatedTarget as Node | null;\n if (related && this.overlayRef?.overlayElement.contains(related)) return;\n this.isOpen.set(false);\n }\n\n // Overlay management\n\n private openOverlay(): void {\n if (this.overlayRef) return;\n\n // On small viewports (when opted in) detach from the trigger and present\n // the menu as a full-width slide-up sheet pinned to the bottom edge.\n const asSheet = wrPresentAsSheet(this.responsive(), this.responsiveConfig);\n\n const positionStrategy = asSheet\n ? this.overlay.position().global().centerHorizontally().bottom('0')\n : this.overlay\n .position()\n .flexibleConnectedTo(this.host)\n .withPositions(WR_DROPDOWN_POSITIONS[this.position()])\n .withPush(true);\n\n this.overlayRef = this.overlay.create({\n positionStrategy,\n scrollStrategy: asSheet ? this.scrollStrategies.block() : this.scrollStrategies.reposition(),\n width: asSheet ? '100%' : undefined,\n hasBackdrop: asSheet,\n backdropClass: asSheet ? 'wr-overlay-backdrop' : undefined,\n panelClass: asSheet\n ? ['wr-dropdown-overlay', 'wr-overlay-sheet']\n : ['wr-dropdown-overlay', `wr-dropdown-overlay--${this.position()}`],\n });\n\n if (asSheet) {\n this.overlayRef\n .backdropClick()\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(() => this.isOpen.set(false));\n }\n\n // Wire trigger id into menu so it can render aria-labelledby.\n this.menu().triggerId.set(this.triggerId);\n\n const portal = new TemplatePortal(this.menu().contentTpl(), this.vcr);\n this.overlayRef.attach(portal);\n\n // Focus the first menu item after the menu renders — unless a POINTER\n // opened it. This used to be unconditional, so merely sweeping the cursor\n // over a `trigger=\"hover\"` menu yanked the caret out of whatever the user\n // was typing; the APG's own rule is that hover shows a menu and does not\n // move focus into it. A keyboard open (Enter / Space / ArrowDown) still\n // focuses the first item, on a hover trigger too.\n if (!this.openedByPointer) queueMicrotask(() => this.focusItemAt(0));\n\n this.outsideClick\n .outsidePointerEvents(this.overlayRef)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(event => {\n if (this.host.nativeElement.contains(event.target as Node)) return;\n this.isOpen.set(false);\n });\n\n this.overlayRef\n .keydownEvents()\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(event => {\n this.handleMenuKeydown(event);\n });\n\n if (this.trigger() === 'hover') {\n this.overlayRef.overlayElement.addEventListener('mouseleave', this.onOverlayMouseLeave);\n }\n\n this.opened.emit();\n }\n\n private closeOverlay(): void {\n if (!this.overlayRef) return;\n if (this.trigger() === 'hover') {\n this.overlayRef.overlayElement.removeEventListener('mouseleave', this.onOverlayMouseLeave);\n }\n // Take the focus back before the pane goes, or it lands on `<body>` and the\n // next Tab starts from the top of the document. Only when focus is actually\n // inside the menu: closing a menu nobody was in must not steal the caret\n // from wherever the user has since moved.\n const active = document.activeElement;\n const focusWasInside = active instanceof Node && this.overlayRef.overlayElement.contains(active);\n this.overlayRef.dispose();\n this.overlayRef = null;\n if (focusWasInside) this.host.nativeElement.focus();\n this.closed.emit();\n }\n\n // Keyboard handling\n\n /** @internal Trigger keydown — ArrowDown/Up/Enter/Space open the menu. */\n protected onKeydown(event: KeyboardEvent): void {\n if (this.isOpen()) return;\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n // Opened from the keyboard — including on a `hover` trigger, which stays\n // keyboard-operable — so the first item SHOULD take focus.\n this.openedByPointer = false;\n this.open();\n }\n }\n\n private handleMenuKeydown(event: KeyboardEvent): void {\n const items = this.getItems();\n if (items.length === 0 && event.key !== 'Escape' && event.key !== 'Tab') return;\n const current = document.activeElement as HTMLElement | null;\n const idx = current ? items.indexOf(current) : -1;\n\n // CDK's keyboard dispatcher feeds this overlay EVERY document keydown, not\n // just the ones typed into it — and a `trigger=\"hover\"` menu is open with\n // the caret deliberately left wherever the user was (see `openedByPointer`).\n // So ownership is checked here rather than assumed: without it, ArrowDown /\n // ArrowUp / Home / End pressed in an unrelated field were `preventDefault`ed\n // and yanked focus into the menu. The trigger counts as ours — a hover menu\n // that the user has tabbed back to should still open to the keyboard.\n const typedIn = event.target;\n const ours =\n typedIn instanceof Node &&\n (this.host.nativeElement.contains(typedIn) || (this.overlayRef?.overlayElement.contains(typedIn) ?? false));\n // Escape (and Tab) still close from anywhere — routing to the topmost\n // overlay regardless of focus is the dispatcher's whole job. Escape just\n // must not drag the caret onto the trigger from a field the user is in.\n if (!ours && event.key !== 'Escape' && event.key !== 'Tab') return;\n\n switch (event.key) {\n case 'Escape':\n event.preventDefault();\n this.isOpen.set(false);\n if (ours) this.host.nativeElement.focus();\n break;\n case 'Tab':\n // Let focus leave naturally; close the menu.\n this.isOpen.set(false);\n break;\n case 'ArrowDown':\n event.preventDefault();\n this.focusItemAt(idx < items.length - 1 ? idx + 1 : 0);\n break;\n case 'ArrowUp':\n event.preventDefault();\n this.focusItemAt(idx > 0 ? idx - 1 : items.length - 1);\n break;\n case 'Home':\n event.preventDefault();\n this.focusItemAt(0);\n break;\n case 'End':\n event.preventDefault();\n this.focusItemAt(items.length - 1);\n break;\n default:\n break;\n }\n }\n\n private getItems(): readonly HTMLElement[] {\n if (!this.overlayRef) return [];\n return Array.from(\n this.overlayRef.overlayElement.querySelectorAll<HTMLElement>('.wr-dropdown-item:not(.wr-dropdown-item--disabled)')\n );\n }\n\n private focusItemAt(index: number): void {\n const items = this.getItems();\n if (items.length === 0) return;\n const clamped = ((index % items.length) + items.length) % items.length;\n items[clamped]?.focus();\n }\n\n private readonly onOverlayMouseLeave = (event: MouseEvent): void => {\n const related = event.relatedTarget as Node | null;\n if (related && this.host.nativeElement.contains(related)) return;\n this.isOpen.set(false);\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, TemplateRef, ViewEncapsulation, signal, viewChild } from '@angular/core';\n\nlet uid = 0;\n\n/**\n * Holds the menu content rendered when a {@link WrDropdown} opens.\n *\n * The component itself doesn't render anywhere — the directive grabs its\n * internal `<ng-template>` and portals it into an overlay.\n *\n * @example\n * ```html\n * <button [wrDropdown]=\"menu\">Actions</button>\n * <wr-dropdown-menu #menu>\n * <wr-dropdown-item icon=\"copy\">Copy</wr-dropdown-item>\n * <wr-dropdown-item icon=\"trash\">Delete</wr-dropdown-item>\n * </wr-dropdown-menu>\n * ```\n *\n * @see https://ngwr.dev/reference/components/dropdown\n */\n@Component({\n selector: 'wr-dropdown-menu',\n template:\n '<ng-template><div class=\"wr-dropdown-menu\" role=\"menu\" [attr.id]=\"menuId()\" [attr.aria-labelledby]=\"triggerId() || null\"><ng-content /></div></ng-template>',\n exportAs: 'wrDropdownMenu',\n encapsulation: ViewEncapsulation.None,\n // A BOUND display rather than a static `style` attribute: Angular writes a\n // binding through `style.setProperty`, which no CSP governs, while a real\n // `style=\"…\"` attribute is refused under `style-src 'self'` — and this host\n // would then lay out an empty box in the middle of the content.\n host: { '[style.display]': \"'none'\" },\n})\nexport class WrDropdownMenu {\n /** The internal template the directive portals into the overlay. @internal */\n readonly contentTpl = viewChild.required(TemplateRef);\n\n /** Stable id used on the menu element and referenced from the trigger's `aria-controls`. */\n readonly menuId = signal(`wr-dropdown-menu-${++uid}`);\n\n /** Trigger element id, set by the directive for `aria-labelledby` wiring. */\n readonly triggerId = signal<string | null>(null);\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 { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ElementRef, ViewEncapsulation, computed, inject, input } from '@angular/core';\n\nimport { WrIcon, type WrIconName } from 'ngwr/icon';\n\n/**\n * Single row inside a `<wr-dropdown-menu>`.\n *\n * @example\n * ```html\n * <wr-dropdown-item icon=\"copy\" (click)=\"copy()\">Copy</wr-dropdown-item>\n * <wr-dropdown-item icon=\"trash\" disabled>Delete</wr-dropdown-item>\n * ```\n */\n@Component({\n selector: 'wr-dropdown-item',\n templateUrl: './dropdown-item.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n role: 'menuitem',\n '[class]': 'classes()',\n '[attr.tabindex]': '-1',\n '[attr.aria-disabled]': 'disabled() ? true : null',\n '(keydown.enter)': 'activate($event)',\n '(keydown.space)': 'activate($event)',\n },\n imports: [WrIcon],\n})\nexport class WrDropdownItem {\n /** Optional leading icon name. @default null */\n readonly icon = input<WrIconName | null>(null);\n\n /** Disable interaction (suppresses pointer + keyboard). @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n protected readonly classes = computed(() => {\n const parts = ['wr-dropdown-item'];\n if (this.disabled()) parts.push('wr-dropdown-item--disabled');\n return parts.join(' ');\n });\n\n private readonly host = inject<ElementRef<HTMLElement>>(ElementRef);\n\n /** @internal Keyboard activation — fires the same click consumers bind to. */\n protected activate(event: Event): void {\n if (this.disabled()) return;\n event.preventDefault();\n this.host.nativeElement.click();\n }\n}\n","@if (icon(); as name) {\n <wr-icon class=\"wr-dropdown-item__icon\" [name]=\"name\" />\n}\n<span class=\"wr-dropdown-item__label\">\n <ng-content />\n</span>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/fesm2022/ngwr-empty.mjs
CHANGED
|
@@ -58,10 +58,10 @@ class WrEmpty {
|
|
|
58
58
|
title = input(null, /* @ts-ignore */
|
|
59
59
|
...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
60
60
|
resolvedTitle = useI18nText(this.title, 'empty.noData', 'No data');
|
|
61
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
62
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
61
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrEmpty, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
62
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: WrEmpty, isStandalone: true, selector: "wr-empty", inputs: { icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconName: { classPropertyName: "iconName", publicName: "iconName", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "status" }, classAttribute: "wr-empty" }, ngImport: i0, template: "@if (iconName(); as name) {\n <wr-icon class=\"wr-empty__icon\" [name]=\"name\" />\n} @else if (icon()) {\n <svg\n class=\"wr-icon__svg wr-empty__icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\"\n />\n </svg>\n}\n<div class=\"wr-empty__title\">{{ resolvedTitle() }}</div>\n<div class=\"wr-empty__description\">\n <ng-content />\n</div>\n<div class=\"wr-empty__actions\">\n <ng-content select=\"[wrEmptyActions]\" />\n</div>\n", dependencies: [{ kind: "component", type: WrIcon, selector: "wr-icon", inputs: ["name"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
63
63
|
}
|
|
64
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
64
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrEmpty, decorators: [{
|
|
65
65
|
type: Component,
|
|
66
66
|
args: [{ selector: 'wr-empty', encapsulation: ViewEncapsulation.None, host: { class: 'wr-empty', role: 'status' }, imports: [WrIcon], template: "@if (iconName(); as name) {\n <wr-icon class=\"wr-empty__icon\" [name]=\"name\" />\n} @else if (icon()) {\n <svg\n class=\"wr-icon__svg wr-empty__icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\"\n />\n </svg>\n}\n<div class=\"wr-empty__title\">{{ resolvedTitle() }}</div>\n<div class=\"wr-empty__description\">\n <ng-content />\n</div>\n<div class=\"wr-empty__actions\">\n <ng-content select=\"[wrEmptyActions]\" />\n</div>\n" }]
|
|
67
67
|
}], propDecorators: { icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconName: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconName", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }] } });
|
|
@@ -30,10 +30,10 @@ import { numAttr } from 'ngwr/utils';
|
|
|
30
30
|
*/
|
|
31
31
|
class WrCalendarEventTemplate {
|
|
32
32
|
template = inject(TemplateRef);
|
|
33
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
34
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.
|
|
33
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrCalendarEventTemplate, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
34
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.5", type: WrCalendarEventTemplate, isStandalone: true, selector: "ng-template[wrCalendarEvent]", ngImport: i0 });
|
|
35
35
|
}
|
|
36
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
36
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrCalendarEventTemplate, decorators: [{
|
|
37
37
|
type: Directive,
|
|
38
38
|
args: [{
|
|
39
39
|
selector: 'ng-template[wrCalendarEvent]',
|
|
@@ -786,10 +786,10 @@ class WrEventCalendar {
|
|
|
786
786
|
return 'ArrowRight';
|
|
787
787
|
return key;
|
|
788
788
|
}
|
|
789
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
790
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrEventCalendar, isStandalone: true, selector: "wr-event-calendar", inputs: { events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, slotMinutes: { classPropertyName: "slotMinutes", publicName: "slotMinutes", isSignal: true, isRequired: false, transformFunction: null }, dayStartHour: { classPropertyName: "dayStartHour", publicName: "dayStartHour", isSignal: true, isRequired: false, transformFunction: null }, dayEndHour: { classPropertyName: "dayEndHour", publicName: "dayEndHour", isSignal: true, isRequired: false, transformFunction: null }, maxLanes: { classPropertyName: "maxLanes", publicName: "maxLanes", isSignal: true, isRequired: false, transformFunction: null }, hideHeader: { classPropertyName: "hideHeader", publicName: "hideHeader", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { view: "viewChange", date: "dateChange", eventClick: "eventClick", slotClick: "slotClick", eventChange: "eventChange" }, host: { listeners: { "pointerup": "onPointerUp($event)", "pointercancel": "drag.set(null)" }, properties: { "class": "classes()" } }, queries: [{ propertyName: "chipTpl", first: true, predicate: WrCalendarEventTemplate, descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n Two grids, one rule: a chip always lives inside the `role=\"gridcell\"` where it\n STARTS, and reaches out from there \u2014 `calc()` widths for day-spanning bands,\n percentage heights for timed chips. A floating events layer would read better\n in the template and would leave `role=\"row\"` owning something other than\n cells, which is exactly what the axe gate rejects.\n-->\n@if (!hideHeader()) {\n <div class=\"wr-event-calendar__header\">\n <div class=\"wr-event-calendar__nav\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-event-calendar__step wr-event-calendar__step--prev\"\n [attr.aria-label]=\"previousLabel()\"\n (click)=\"step(-1)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n <button wr-btn type=\"button\" size=\"sm\" outlined class=\"wr-event-calendar__today\" (click)=\"goToday()\">\n {{ todayLabel() }}\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-event-calendar__step wr-event-calendar__step--next\"\n [attr.aria-label]=\"nextLabel()\"\n (click)=\"step(1)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n </div>\n\n <h2 class=\"wr-event-calendar__title\">{{ title() }}</h2>\n\n @if (switcher().length > 1) {\n <wr-btn-group>\n @for (item of switcher(); track item.view) {\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n class=\"wr-event-calendar__view\"\n [outlined]=\"view() !== item.view\"\n [color]=\"view() === item.view ? 'primary' : null\"\n [attr.aria-pressed]=\"view() === item.view\"\n (click)=\"setView(item.view)\"\n >\n {{ item.label }}\n </button>\n }\n </wr-btn-group>\n }\n </div>\n}\n\n@if (view() === 'month') {\n <div\n role=\"grid\"\n class=\"wr-event-calendar__grid wr-event-calendar__grid--month\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n >\n <div role=\"row\" class=\"wr-event-calendar__row\">\n @for (name of weekdayNames(); track $index) {\n <div role=\"columnheader\" class=\"wr-event-calendar__weekday\">{{ name }}</div>\n }\n </div>\n\n @for (week of weeks(); track week.key; let w = $index) {\n <div role=\"row\" class=\"wr-event-calendar__row\">\n @for (cell of week.days; track cell.key; let c = $index) {\n @let index = w * 7 + c;\n <div\n role=\"gridcell\"\n class=\"wr-event-calendar__cell wr-event-calendar__cell--day\"\n [class.wr-event-calendar__cell--muted]=\"!cell.inMonth\"\n [class.wr-event-calendar__cell--today]=\"cell.isToday\"\n [attr.aria-label]=\"cell.label\"\n [attr.tabindex]=\"isCursor(index, -1) ? 0 : -1\"\n [attr.data-cell-date]=\"stamp(cell.date)\"\n [attr.data-cell-minutes]=\"-1\"\n [attr.data-cell-index]=\"`${index}:-1`\"\n (focusin)=\"onCellFocus(index, -1)\"\n (click)=\"onCellClick(cell.date, -1)\"\n (keydown)=\"onGridKeyDown($event)\"\n >\n <span class=\"wr-event-calendar__daynum\" aria-hidden=\"true\">{{ cell.day }}</span>\n\n @for (chip of cell.bands; track chip.key) {\n <ng-container\n [ngTemplateOutlet]=\"band\"\n [ngTemplateOutletContext]=\"{ $implicit: chip, offset: '1.75rem' }\"\n />\n }\n\n @if (cell.hidden > 0) {\n <button\n type=\"button\"\n class=\"wr-event-calendar__more\"\n [style.--wr-event-calendar-lane]=\"maxLanes()\"\n (click)=\"openDay(cell.date); $event.stopPropagation()\"\n >\n {{ moreLabel({ count: cell.hidden }) }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n} @else {\n <div\n role=\"grid\"\n class=\"wr-event-calendar__grid wr-event-calendar__grid--time\"\n [style.--wr-event-calendar-cols]=\"columns().length\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n >\n <div role=\"row\" class=\"wr-event-calendar__row\">\n <div role=\"columnheader\" class=\"wr-event-calendar__gutter\" [attr.aria-label]=\"timeLabel()\"></div>\n @for (column of columns(); track column.key) {\n <div\n role=\"columnheader\"\n class=\"wr-event-calendar__colhead\"\n [class.wr-event-calendar__colhead--today]=\"column.isToday\"\n >\n <span class=\"wr-event-calendar__colhead-weekday\">{{ column.weekday }}</span>\n <span class=\"wr-event-calendar__colhead-day\">{{ column.day }}</span>\n </div>\n }\n </div>\n\n @if (hasAllDay()) {\n <div role=\"row\" class=\"wr-event-calendar__row wr-event-calendar__row--allday\">\n <div role=\"rowheader\" class=\"wr-event-calendar__gutter\">{{ allDayLabel() }}</div>\n @for (cell of allDayCells(); track cell.key; let c = $index) {\n <div\n role=\"gridcell\"\n class=\"wr-event-calendar__cell wr-event-calendar__cell--allday\"\n [attr.aria-label]=\"cell.label\"\n [attr.tabindex]=\"isCursor(c, -1) ? 0 : -1\"\n [attr.data-cell-date]=\"stamp(cell.date)\"\n [attr.data-cell-minutes]=\"-1\"\n [attr.data-cell-index]=\"`${c}:-1`\"\n (focusin)=\"onCellFocus(c, -1)\"\n (click)=\"onCellClick(cell.date, -1)\"\n (keydown)=\"onGridKeyDown($event)\"\n >\n @for (chip of cell.bands; track chip.key) {\n <ng-container [ngTemplateOutlet]=\"band\" [ngTemplateOutletContext]=\"{ $implicit: chip, offset: '0rem' }\" />\n }\n </div>\n }\n </div>\n }\n\n @for (row of rows(); track row.key) {\n <div role=\"row\" class=\"wr-event-calendar__row\" [class.wr-event-calendar__row--major]=\"row.major\">\n <div role=\"rowheader\" class=\"wr-event-calendar__gutter\">{{ row.label }}</div>\n @for (cell of row.cells; track cell.key; let c = $index) {\n <div\n role=\"gridcell\"\n class=\"wr-event-calendar__cell wr-event-calendar__cell--slot\"\n [class.wr-event-calendar__cell--major]=\"row.major\"\n [attr.aria-label]=\"cell.label\"\n [attr.tabindex]=\"isCursor(c, row.minutes) ? 0 : -1\"\n [attr.data-cell-date]=\"stamp(cell.date)\"\n [attr.data-cell-minutes]=\"row.minutes\"\n [attr.data-cell-index]=\"`${c}:${row.minutes}`\"\n (focusin)=\"onCellFocus(c, row.minutes)\"\n (click)=\"onCellClick(cell.date, row.minutes)\"\n (keydown)=\"onGridKeyDown($event)\"\n >\n @for (chip of cell.chips; track chip.key) {\n <button\n type=\"button\"\n class=\"wr-event-calendar__chip wr-event-calendar__chip--time\"\n [class.wr-event-calendar__chip--dragging]=\"dragging() === chip.event.id\"\n [class.wr-event-calendar__chip--continues-before]=\"chip.continuesBefore\"\n [class.wr-event-calendar__chip--continues-after]=\"chip.continuesAfter\"\n [style.--wr-event-calendar-color]=\"`var(--wr-color-${chip.event.color ?? 'primary'})`\"\n [style.top.%]=\"chip.top\"\n [style.height.%]=\"chip.height\"\n [style.inset-inline-start.%]=\"chip.inlineStart\"\n [style.width.%]=\"chip.width\"\n [attr.aria-label]=\"chipLabel(chip.event)\"\n (click)=\"onChipClick(chip.event); $event.stopPropagation()\"\n (keydown)=\"onChipKeyDown($event, chip.event)\"\n (pointerdown)=\"onChipPointerDown($event, chip.event, 'move')\"\n >\n <span class=\"wr-event-calendar__chip-time\" aria-hidden=\"true\">{{ chipTime(chip.event) }}</span>\n @if (chipTemplate(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"{ $implicit: chip.event }\" />\n } @else {\n <span class=\"wr-event-calendar__chip-title\" aria-hidden=\"true\">{{ chip.event.title }}</span>\n }\n\n @if (canDrag(chip.event) && !chip.continuesAfter) {\n <!--\n A grab area, not a control: the same resize is on the keyboard as\n `Alt` + `Shift` + arrows on the chip itself, so exposing a second\n focusable thing here would add a tab stop that leads nowhere.\n -->\n <span\n class=\"wr-event-calendar__resize\"\n aria-hidden=\"true\"\n (pointerdown)=\"onChipPointerDown($event, chip.event, 'resize')\"\n ></span>\n }\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n\n<!-- One chip shape for both day-spanning cases: month cells and the all-day band. -->\n<ng-template #band let-chip let-offset=\"offset\">\n <button\n type=\"button\"\n class=\"wr-event-calendar__chip wr-event-calendar__chip--band\"\n [class.wr-event-calendar__chip--dragging]=\"dragging() === chip.event.id\"\n [class.wr-event-calendar__chip--continues-before]=\"chip.continuesBefore\"\n [class.wr-event-calendar__chip--continues-after]=\"chip.continuesAfter\"\n [style.--wr-event-calendar-color]=\"`var(--wr-color-${chip.event.color ?? 'primary'})`\"\n [style.--wr-event-calendar-span]=\"chip.span\"\n [style.--wr-event-calendar-lane]=\"chip.lane\"\n [style.--wr-event-calendar-offset]=\"offset\"\n [attr.aria-label]=\"chipLabel(chip.event)\"\n (click)=\"onChipClick(chip.event); $event.stopPropagation()\"\n (keydown)=\"onChipKeyDown($event, chip.event)\"\n (pointerdown)=\"onChipPointerDown($event, chip.event, 'move')\"\n >\n @if (chipTemplate(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"{ $implicit: chip.event }\" />\n } @else {\n <span class=\"wr-event-calendar__chip-title\" aria-hidden=\"true\">{{ chip.event.title }}</span>\n }\n </button>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: WrButton, selector: "wr-btn, button[wr-btn], a[wr-btn]", inputs: ["color", "size", "shape", "icon", "iconPosition", "disabled", "outlined", "block", "loading", "disabledWhenLoading"] }, { kind: "component", type: WrButtonGroup, selector: "wr-btn-group", inputs: ["shape"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
789
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrEventCalendar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
790
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: WrEventCalendar, isStandalone: true, selector: "wr-event-calendar", inputs: { events: { classPropertyName: "events", publicName: "events", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, date: { classPropertyName: "date", publicName: "date", isSignal: true, isRequired: false, transformFunction: null }, views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, slotMinutes: { classPropertyName: "slotMinutes", publicName: "slotMinutes", isSignal: true, isRequired: false, transformFunction: null }, dayStartHour: { classPropertyName: "dayStartHour", publicName: "dayStartHour", isSignal: true, isRequired: false, transformFunction: null }, dayEndHour: { classPropertyName: "dayEndHour", publicName: "dayEndHour", isSignal: true, isRequired: false, transformFunction: null }, maxLanes: { classPropertyName: "maxLanes", publicName: "maxLanes", isSignal: true, isRequired: false, transformFunction: null }, hideHeader: { classPropertyName: "hideHeader", publicName: "hideHeader", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { view: "viewChange", date: "dateChange", eventClick: "eventClick", slotClick: "slotClick", eventChange: "eventChange" }, host: { listeners: { "pointerup": "onPointerUp($event)", "pointercancel": "drag.set(null)" }, properties: { "class": "classes()" } }, queries: [{ propertyName: "chipTpl", first: true, predicate: WrCalendarEventTemplate, descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n Two grids, one rule: a chip always lives inside the `role=\"gridcell\"` where it\n STARTS, and reaches out from there \u2014 `calc()` widths for day-spanning bands,\n percentage heights for timed chips. A floating events layer would read better\n in the template and would leave `role=\"row\"` owning something other than\n cells, which is exactly what the axe gate rejects.\n-->\n@if (!hideHeader()) {\n <div class=\"wr-event-calendar__header\">\n <div class=\"wr-event-calendar__nav\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-event-calendar__step wr-event-calendar__step--prev\"\n [attr.aria-label]=\"previousLabel()\"\n (click)=\"step(-1)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n <button wr-btn type=\"button\" size=\"sm\" outlined class=\"wr-event-calendar__today\" (click)=\"goToday()\">\n {{ todayLabel() }}\n </button>\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-event-calendar__step wr-event-calendar__step--next\"\n [attr.aria-label]=\"nextLabel()\"\n (click)=\"step(1)\"\n >\n <svg\n class=\"wr-icon__svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n </div>\n\n <h2 class=\"wr-event-calendar__title\">{{ title() }}</h2>\n\n @if (switcher().length > 1) {\n <wr-btn-group>\n @for (item of switcher(); track item.view) {\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n class=\"wr-event-calendar__view\"\n [outlined]=\"view() !== item.view\"\n [color]=\"view() === item.view ? 'primary' : null\"\n [attr.aria-pressed]=\"view() === item.view\"\n (click)=\"setView(item.view)\"\n >\n {{ item.label }}\n </button>\n }\n </wr-btn-group>\n }\n </div>\n}\n\n@if (view() === 'month') {\n <div\n role=\"grid\"\n class=\"wr-event-calendar__grid wr-event-calendar__grid--month\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n >\n <div role=\"row\" class=\"wr-event-calendar__row\">\n @for (name of weekdayNames(); track $index) {\n <div role=\"columnheader\" class=\"wr-event-calendar__weekday\">{{ name }}</div>\n }\n </div>\n\n @for (week of weeks(); track week.key; let w = $index) {\n <div role=\"row\" class=\"wr-event-calendar__row\">\n @for (cell of week.days; track cell.key; let c = $index) {\n @let index = w * 7 + c;\n <div\n role=\"gridcell\"\n class=\"wr-event-calendar__cell wr-event-calendar__cell--day\"\n [class.wr-event-calendar__cell--muted]=\"!cell.inMonth\"\n [class.wr-event-calendar__cell--today]=\"cell.isToday\"\n [attr.aria-label]=\"cell.label\"\n [attr.tabindex]=\"isCursor(index, -1) ? 0 : -1\"\n [attr.data-cell-date]=\"stamp(cell.date)\"\n [attr.data-cell-minutes]=\"-1\"\n [attr.data-cell-index]=\"`${index}:-1`\"\n (focusin)=\"onCellFocus(index, -1)\"\n (click)=\"onCellClick(cell.date, -1)\"\n (keydown)=\"onGridKeyDown($event)\"\n >\n <span class=\"wr-event-calendar__daynum\" aria-hidden=\"true\">{{ cell.day }}</span>\n\n @for (chip of cell.bands; track chip.key) {\n <ng-container\n [ngTemplateOutlet]=\"band\"\n [ngTemplateOutletContext]=\"{ $implicit: chip, offset: '1.75rem' }\"\n />\n }\n\n @if (cell.hidden > 0) {\n <button\n type=\"button\"\n class=\"wr-event-calendar__more\"\n [style.--wr-event-calendar-lane]=\"maxLanes()\"\n (click)=\"openDay(cell.date); $event.stopPropagation()\"\n >\n {{ moreLabel({ count: cell.hidden }) }}\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n} @else {\n <div\n role=\"grid\"\n class=\"wr-event-calendar__grid wr-event-calendar__grid--time\"\n [style.--wr-event-calendar-cols]=\"columns().length\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n >\n <div role=\"row\" class=\"wr-event-calendar__row\">\n <div role=\"columnheader\" class=\"wr-event-calendar__gutter\" [attr.aria-label]=\"timeLabel()\"></div>\n @for (column of columns(); track column.key) {\n <div\n role=\"columnheader\"\n class=\"wr-event-calendar__colhead\"\n [class.wr-event-calendar__colhead--today]=\"column.isToday\"\n >\n <span class=\"wr-event-calendar__colhead-weekday\">{{ column.weekday }}</span>\n <span class=\"wr-event-calendar__colhead-day\">{{ column.day }}</span>\n </div>\n }\n </div>\n\n @if (hasAllDay()) {\n <div role=\"row\" class=\"wr-event-calendar__row wr-event-calendar__row--allday\">\n <div role=\"rowheader\" class=\"wr-event-calendar__gutter\">{{ allDayLabel() }}</div>\n @for (cell of allDayCells(); track cell.key; let c = $index) {\n <div\n role=\"gridcell\"\n class=\"wr-event-calendar__cell wr-event-calendar__cell--allday\"\n [attr.aria-label]=\"cell.label\"\n [attr.tabindex]=\"isCursor(c, -1) ? 0 : -1\"\n [attr.data-cell-date]=\"stamp(cell.date)\"\n [attr.data-cell-minutes]=\"-1\"\n [attr.data-cell-index]=\"`${c}:-1`\"\n (focusin)=\"onCellFocus(c, -1)\"\n (click)=\"onCellClick(cell.date, -1)\"\n (keydown)=\"onGridKeyDown($event)\"\n >\n @for (chip of cell.bands; track chip.key) {\n <ng-container [ngTemplateOutlet]=\"band\" [ngTemplateOutletContext]=\"{ $implicit: chip, offset: '0rem' }\" />\n }\n </div>\n }\n </div>\n }\n\n @for (row of rows(); track row.key) {\n <div role=\"row\" class=\"wr-event-calendar__row\" [class.wr-event-calendar__row--major]=\"row.major\">\n <div role=\"rowheader\" class=\"wr-event-calendar__gutter\">{{ row.label }}</div>\n @for (cell of row.cells; track cell.key; let c = $index) {\n <div\n role=\"gridcell\"\n class=\"wr-event-calendar__cell wr-event-calendar__cell--slot\"\n [class.wr-event-calendar__cell--major]=\"row.major\"\n [attr.aria-label]=\"cell.label\"\n [attr.tabindex]=\"isCursor(c, row.minutes) ? 0 : -1\"\n [attr.data-cell-date]=\"stamp(cell.date)\"\n [attr.data-cell-minutes]=\"row.minutes\"\n [attr.data-cell-index]=\"`${c}:${row.minutes}`\"\n (focusin)=\"onCellFocus(c, row.minutes)\"\n (click)=\"onCellClick(cell.date, row.minutes)\"\n (keydown)=\"onGridKeyDown($event)\"\n >\n @for (chip of cell.chips; track chip.key) {\n <button\n type=\"button\"\n class=\"wr-event-calendar__chip wr-event-calendar__chip--time\"\n [class.wr-event-calendar__chip--dragging]=\"dragging() === chip.event.id\"\n [class.wr-event-calendar__chip--continues-before]=\"chip.continuesBefore\"\n [class.wr-event-calendar__chip--continues-after]=\"chip.continuesAfter\"\n [style.--wr-event-calendar-color]=\"`var(--wr-color-${chip.event.color ?? 'primary'})`\"\n [style.top.%]=\"chip.top\"\n [style.height.%]=\"chip.height\"\n [style.inset-inline-start.%]=\"chip.inlineStart\"\n [style.width.%]=\"chip.width\"\n [attr.aria-label]=\"chipLabel(chip.event)\"\n (click)=\"onChipClick(chip.event); $event.stopPropagation()\"\n (keydown)=\"onChipKeyDown($event, chip.event)\"\n (pointerdown)=\"onChipPointerDown($event, chip.event, 'move')\"\n >\n <span class=\"wr-event-calendar__chip-time\" aria-hidden=\"true\">{{ chipTime(chip.event) }}</span>\n @if (chipTemplate(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"{ $implicit: chip.event }\" />\n } @else {\n <span class=\"wr-event-calendar__chip-title\" aria-hidden=\"true\">{{ chip.event.title }}</span>\n }\n\n @if (canDrag(chip.event) && !chip.continuesAfter) {\n <!--\n A grab area, not a control: the same resize is on the keyboard as\n `Alt` + `Shift` + arrows on the chip itself, so exposing a second\n focusable thing here would add a tab stop that leads nowhere.\n -->\n <span\n class=\"wr-event-calendar__resize\"\n aria-hidden=\"true\"\n (pointerdown)=\"onChipPointerDown($event, chip.event, 'resize')\"\n ></span>\n }\n </button>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n\n<!-- One chip shape for both day-spanning cases: month cells and the all-day band. -->\n<ng-template #band let-chip let-offset=\"offset\">\n <button\n type=\"button\"\n class=\"wr-event-calendar__chip wr-event-calendar__chip--band\"\n [class.wr-event-calendar__chip--dragging]=\"dragging() === chip.event.id\"\n [class.wr-event-calendar__chip--continues-before]=\"chip.continuesBefore\"\n [class.wr-event-calendar__chip--continues-after]=\"chip.continuesAfter\"\n [style.--wr-event-calendar-color]=\"`var(--wr-color-${chip.event.color ?? 'primary'})`\"\n [style.--wr-event-calendar-span]=\"chip.span\"\n [style.--wr-event-calendar-lane]=\"chip.lane\"\n [style.--wr-event-calendar-offset]=\"offset\"\n [attr.aria-label]=\"chipLabel(chip.event)\"\n (click)=\"onChipClick(chip.event); $event.stopPropagation()\"\n (keydown)=\"onChipKeyDown($event, chip.event)\"\n (pointerdown)=\"onChipPointerDown($event, chip.event, 'move')\"\n >\n @if (chipTemplate(); as tpl) {\n <ng-container [ngTemplateOutlet]=\"tpl\" [ngTemplateOutletContext]=\"{ $implicit: chip.event }\" />\n } @else {\n <span class=\"wr-event-calendar__chip-title\" aria-hidden=\"true\">{{ chip.event.title }}</span>\n }\n </button>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: WrButton, selector: "wr-btn, button[wr-btn], a[wr-btn]", inputs: ["color", "size", "shape", "icon", "iconPosition", "disabled", "outlined", "block", "loading", "disabledWhenLoading"] }, { kind: "component", type: WrButtonGroup, selector: "wr-btn-group", inputs: ["shape"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
791
791
|
}
|
|
792
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
792
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrEventCalendar, decorators: [{
|
|
793
793
|
type: Component,
|
|
794
794
|
args: [{ selector: 'wr-event-calendar', encapsulation: ViewEncapsulation.None, host: {
|
|
795
795
|
'[class]': 'classes()',
|
|
@@ -330,10 +330,10 @@ class WrFallingText {
|
|
|
330
330
|
const rect = host.getBoundingClientRect();
|
|
331
331
|
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
|
|
332
332
|
}
|
|
333
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
334
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
333
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrFallingText, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
334
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: WrFallingText, isStandalone: true, selector: "wr-falling-text", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: true, transformFunction: null }, highlightWords: { classPropertyName: "highlightWords", publicName: "highlightWords", isSignal: true, isRequired: false, transformFunction: null }, trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: true, isRequired: false, transformFunction: null }, gravity: { classPropertyName: "gravity", publicName: "gravity", isSignal: true, isRequired: false, transformFunction: null }, fontSize: { classPropertyName: "fontSize", publicName: "fontSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "wr-falling-text" }, viewQueries: [{ propertyName: "wordsRef", first: true, predicate: ["wordsContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div #wordsContainer class=\"wr-falling-text__words\" [style.font-size]=\"fontSize()\">\n @for (w of words(); track $index) {\n <span class=\"wr-falling-text__word\" [class.wr-falling-text__word--hl]=\"w.highlighted\">{{ w.text }}</span>\n @if (!$last) {\n <span class=\"wr-falling-text__space\"> </span>\n }\n }\n</div>\n", styles: [".wr-falling-text{position:relative;display:block;overflow:hidden;touch-action:none}.wr-falling-text__words{line-height:1.4}.wr-falling-text__word{display:inline-block;will-change:transform;-webkit-user-select:none;user-select:none;cursor:grab}.wr-falling-text__word:active{cursor:grabbing}.wr-falling-text__word--hl{color:var(--wr-color-primary-ink)}.wr-falling-text__space{display:inline;white-space:pre}\n"], encapsulation: i0.ViewEncapsulation.None });
|
|
335
335
|
}
|
|
336
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
336
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrFallingText, decorators: [{
|
|
337
337
|
type: Component,
|
|
338
338
|
args: [{ selector: 'wr-falling-text', encapsulation: ViewEncapsulation.None, host: { class: 'wr-falling-text' }, template: "<div #wordsContainer class=\"wr-falling-text__words\" [style.font-size]=\"fontSize()\">\n @for (w of words(); track $index) {\n <span class=\"wr-falling-text__word\" [class.wr-falling-text__word--hl]=\"w.highlighted\">{{ w.text }}</span>\n @if (!$last) {\n <span class=\"wr-falling-text__space\"> </span>\n }\n }\n</div>\n", styles: [".wr-falling-text{position:relative;display:block;overflow:hidden;touch-action:none}.wr-falling-text__words{line-height:1.4}.wr-falling-text__word{display:inline-block;will-change:transform;-webkit-user-select:none;user-select:none;cursor:grab}.wr-falling-text__word:active{cursor:grabbing}.wr-falling-text__word--hl{color:var(--wr-color-primary-ink)}.wr-falling-text__space{display:inline;white-space:pre}\n"] }]
|
|
339
339
|
}], ctorParameters: () => [], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: true }] }], highlightWords: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightWords", required: false }] }], trigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "trigger", required: false }] }], gravity: [{ type: i0.Input, args: [{ isSignal: true, alias: "gravity", required: false }] }], fontSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "fontSize", required: false }] }], wordsRef: [{ type: i0.ViewChild, args: ['wordsContainer', { isSignal: true }] }] } });
|
|
@@ -303,10 +303,10 @@ class WrFileUpload {
|
|
|
303
303
|
this.value.set(list.length > 0 ? list[0] : null);
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.
|
|
307
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.
|
|
306
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrFileUpload, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
307
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: WrFileUpload, isStandalone: true, selector: "wr-file-upload", inputs: { multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, showList: { classPropertyName: "showList", publicName: "showList", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, pickLabel: { classPropertyName: "pickLabel", publicName: "pickLabel", isSignal: true, isRequired: false, transformFunction: null }, dropLabel: { classPropertyName: "dropLabel", publicName: "dropLabel", isSignal: true, isRequired: false, transformFunction: null }, dropZoneLabel: { classPropertyName: "dropZoneLabel", publicName: "dropZoneLabel", isSignal: true, isRequired: false, transformFunction: null }, removeFileLabel: { classPropertyName: "removeFileLabel", publicName: "removeFileLabel", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", rejected: "rejected", touch: "touch" }, host: { properties: { "class": "classes()" } }, viewQueries: [{ propertyName: "picker", first: true, predicate: ["picker"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"wr-file-upload__zone\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"resolvedDropZone()\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (click)=\"openPicker()\"\n (keydown)=\"onZoneKey($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n <svg class=\"wr-file-upload__icon\" width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n <div class=\"wr-file-upload__hint\">\n <strong>{{ resolvedPick() }}</strong>\n <span>{{ resolvedDrop() }}</span>\n @if (helperText()) {\n <small>{{ helperText() }}</small>\n }\n </div>\n</div>\n\n<!--\n The real <input type=\"file\"> is never operated directly \u2014 the zone above is\n the control and forwards clicks here, so the input is visually hidden and kept\n OUTSIDE the zone: inside, it nested one interactive element within another\n (the zone is role=\"button\") and put two tab stops where users see one. It is\n positioned absolutely and clipped, so being a sibling changes nothing visually.\n aria-hidden must be paired with tabindex=\"-1\", or hiding a focusable element is\n itself a violation.\n-->\n<input\n #picker\n class=\"wr-file-upload__picker\"\n type=\"file\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [attr.accept]=\"accept() || null\"\n [attr.multiple]=\"multiple() ? '' : null\"\n [disabled]=\"disabled() || readonly()\"\n (change)=\"onPickerChange($event)\"\n/>\n\n@if (showList() && files().length > 0) {\n <ul class=\"wr-file-upload__list\">\n @for (file of files(); track $index) {\n <li class=\"wr-file-upload__item\">\n <span class=\"wr-file-upload__name\" [title]=\"file.name\">{{ file.name }}</span>\n <span class=\"wr-file-upload__size\">{{ formatBytes(file.size) }}</span>\n <button\n type=\"button\"\n class=\"wr-file-upload__remove\"\n [attr.aria-label]=\"resolvedRemove()\"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"removeAt($index)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\n <path\n d=\"M2 2 L10 10 M10 2 L2 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </li>\n }\n </ul>\n}\n", encapsulation: i0.ViewEncapsulation.None });
|
|
308
308
|
}
|
|
309
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.
|
|
309
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: WrFileUpload, decorators: [{
|
|
310
310
|
type: Component,
|
|
311
311
|
args: [{ selector: 'wr-file-upload', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [], template: "<div\n class=\"wr-file-upload__zone\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"resolvedDropZone()\"\n [attr.aria-disabled]=\"disabled() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (click)=\"openPicker()\"\n (keydown)=\"onZoneKey($event)\"\n (dragenter)=\"onDragEnter($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n <svg class=\"wr-file-upload__icon\" width=\"32\" height=\"32\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path\n d=\"M12 16V4M12 4l-4 4M12 4l4 4M4 16v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n <div class=\"wr-file-upload__hint\">\n <strong>{{ resolvedPick() }}</strong>\n <span>{{ resolvedDrop() }}</span>\n @if (helperText()) {\n <small>{{ helperText() }}</small>\n }\n </div>\n</div>\n\n<!--\n The real <input type=\"file\"> is never operated directly \u2014 the zone above is\n the control and forwards clicks here, so the input is visually hidden and kept\n OUTSIDE the zone: inside, it nested one interactive element within another\n (the zone is role=\"button\") and put two tab stops where users see one. It is\n positioned absolutely and clipped, so being a sibling changes nothing visually.\n aria-hidden must be paired with tabindex=\"-1\", or hiding a focusable element is\n itself a violation.\n-->\n<input\n #picker\n class=\"wr-file-upload__picker\"\n type=\"file\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n [attr.accept]=\"accept() || null\"\n [attr.multiple]=\"multiple() ? '' : null\"\n [disabled]=\"disabled() || readonly()\"\n (change)=\"onPickerChange($event)\"\n/>\n\n@if (showList() && files().length > 0) {\n <ul class=\"wr-file-upload__list\">\n @for (file of files(); track $index) {\n <li class=\"wr-file-upload__item\">\n <span class=\"wr-file-upload__name\" [title]=\"file.name\">{{ file.name }}</span>\n <span class=\"wr-file-upload__size\">{{ formatBytes(file.size) }}</span>\n <button\n type=\"button\"\n class=\"wr-file-upload__remove\"\n [attr.aria-label]=\"resolvedRemove()\"\n [disabled]=\"disabled() || readonly()\"\n (click)=\"removeAt($index)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\n <path\n d=\"M2 2 L10 10 M10 2 L2 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </button>\n </li>\n }\n </ul>\n}\n" }]
|
|
312
312
|
}], ctorParameters: () => [], propDecorators: { multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], showList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showList", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], pickLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "pickLabel", required: false }] }], dropLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropLabel", required: false }] }], dropZoneLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dropZoneLabel", required: false }] }], removeFileLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "removeFileLabel", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], touch: [{ type: i0.Output, args: ["touch"] }], picker: [{ type: i0.ViewChild, args: ['picker', { isSignal: true }] }] } });
|
|
@@ -130,8 +130,9 @@ class WrFormFieldHarness extends ContentContainerComponentHarness {
|
|
|
130
130
|
*
|
|
131
131
|
* `null` does not mean the consumer set no hint: the error block takes the
|
|
132
132
|
* hint's slot outright, so a field with a visible message has no hint in the DOM
|
|
133
|
-
* at all
|
|
134
|
-
*
|
|
133
|
+
* at all — which is also why the hint's id stops being published at the same
|
|
134
|
+
* moment. Whether the CONTROL announces it is a separate question, and one this
|
|
135
|
+
* harness answers through {@link getAnnouncedDescription}.
|
|
135
136
|
*/
|
|
136
137
|
async getHint() {
|
|
137
138
|
const hint = await this.locatorForOptional(HINT)();
|
|
@@ -251,11 +252,16 @@ class WrFormFieldHarness extends ContentContainerComponentHarness {
|
|
|
251
252
|
/**
|
|
252
253
|
* The ids the projected control names in `aria-describedby`, in order.
|
|
253
254
|
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
255
|
+
* Which id that is depends on the CONTROL, not on the field, and the answer is
|
|
256
|
+
* worth knowing before reading a result. A control that composes the pair with
|
|
257
|
+
* `useFormFieldAria()` names the error block while one is showing and the hint
|
|
258
|
+
* otherwise. A control that reads the field's `describedBy` on its own — the
|
|
259
|
+
* `[wrInput]` directive, `wr-segmented` and `wr-slider` — names the error block
|
|
260
|
+
* and nothing else, so an empty list beside a visible hint is that control's
|
|
261
|
+
* answer rather than a broken field.
|
|
262
|
+
*
|
|
263
|
+
* The two are separate because folding the hint into the one signal announced
|
|
264
|
+
* every hinted field as invalid: those three derive `aria-invalid` from it.
|
|
259
265
|
*/
|
|
260
266
|
async getDescribedByIds() {
|
|
261
267
|
const value = await (await this.control()).getAttribute('aria-describedby');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-form-testing.mjs","sources":["../../../projects/lib/form/testing/wr-form-field-harness.ts","../../../projects/lib/form/testing/wr-form-item-harness.ts","../../../projects/lib/form/testing/ngwr-form-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 {\n ContentContainerComponentHarness,\n type HarnessLoader,\n HarnessPredicate,\n type TestElement,\n} from '@angular/cdk/testing';\n\nimport type { WrFormFieldError, WrFormFieldHarnessFilters } from './interfaces';\n\n/**\n * The messages this field is showing, or the ones it is holding back.\n *\n * Anchored with `:scope >` because the field's own parts are all direct children\n * of its host: a `<wr-form-field>` nested in another one's control slot otherwise\n * has its error block matched by the OUTER field's query too, and the outer\n * answers with a sentence that is not its own.\n */\nconst MESSAGE = ':scope > .wr-form-field__errors > .wr-form-field__error';\nconst VISIBLE = `${MESSAGE}:not(.wr-form-field__error--hidden)`;\nconst SUPPRESSED = `${MESSAGE}.wr-form-field__error--hidden`;\n\n/** The control slot — every native control lookup is scoped through this. */\nconst SLOT = ':scope > .wr-form-field__control';\n\n/** The label and the hint, anchored for the same reason as {@link MESSAGE}. */\nconst LABEL = ':scope > .wr-form-field__label';\nconst HINT = ':scope > .wr-form-field__hint';\n\n/**\n * Test harness for `<wr-form-field>` — the label + control + hint + validation\n * copy block.\n *\n * The field's real job is the COPY CONTRACT, and this harness is built around it.\n * A message reaches the DOM one of two ways, and they are distinguishable there:\n *\n * - a `<wr-form-error key=\"…\">` the consumer wrote, which renders only while that\n * key is failing — the others stay in the DOM carrying\n * `wr-form-field__error--hidden`, which is why {@link getErrors} filters and\n * {@link getSuppressedErrorKeys} exists. A raw `.wr-form-error` query answers\n * with the held-back copy too, and reads green on the wrong sentence.\n * - a message the field RESOLVED for an error no markup claimed:\n * `provideWrFormErrors()`, then the `ngwr/i18n` `validation.*` catalog, then a\n * built-in English sentence. All three render the identical `<div>`, so\n * `source` is `'resolved'` for every one of them — the DOM genuinely cannot say\n * which link answered, and this harness does not pretend otherwise.\n *\n * Nothing renders at all until the projected control is touched or dirty, so a\n * spec has to reach that gate first: {@link blurControl} is the shortest way\n * (blur is what marks a bound control touched), and typing through the control's\n * own harness is the other.\n *\n * The field publishes no `aria-label` anywhere. Its `<label for>` pointing at the\n * projected control IS the naming story, and a control that does not adopt the\n * id leaves the field silently unlabelled — {@link isLabelLinkedToControl} is\n * what catches that. The other half is the reverse wiring: the control reads back\n * `aria-describedby` and `aria-invalid`, which is the only reason a screen reader\n * hears the message ({@link getAnnouncedDescription}).\n *\n * Harnesses loaded through this one resolve INSIDE the control slot, so the\n * control's own harness composes without a selector:\n *\n * @example\n * ```ts\n * const email = await loader.getHarness(WrFormFieldHarness.with({ label: 'Email' }));\n *\n * await (await email.getHarness(WrInputHarness)).setValue('nope');\n * await email.blurControl();\n *\n * expect(await email.getErrorTexts()).toEqual([\"That isn't a valid email.\"]);\n * expect(await email.getAnnouncedDescription()).toBe(\"That isn't a valid email.\");\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrFormFieldHarness extends ContentContainerComponentHarness {\n static hostSelector = 'wr-form-field';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrFormFieldHarnessFilters = {}): HarnessPredicate<WrFormFieldHarness> {\n return new HarnessPredicate(WrFormFieldHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('hint', options.hint, (harness, hint) => HarnessPredicate.stringMatches(harness.getHint(), hint))\n .addOption('required', options.required, async (harness, required) => (await harness.isRequired()) === required)\n .addOption('invalid', options.invalid, async (harness, invalid) => (await harness.isInvalid()) === invalid)\n .addOption('errorText', options.errorText, async (harness, text) => {\n for (const shown of await harness.getErrorTexts()) {\n if (await HarnessPredicate.stringMatches(shown, text)) return true;\n }\n return false;\n });\n }\n\n /**\n * The label text, or `null` when the field renders no label.\n *\n * The `*` and `(optional)` markers ride INSIDE the `<label>`, so they are\n * excluded here — otherwise this answers `'Email *'` and every equality\n * assertion has to know which marker the field happens to carry.\n *\n * `null` is not \"the consumer forgot\": `label=\"\"` renders no `<label>` element\n * at all, which also means there is nothing naming the control.\n */\n async getLabel(): Promise<string | null> {\n const label = await this.labelElement();\n return label ? label.text({ exclude: '.wr-form-field__required, .wr-form-field__optional' }) : null;\n }\n\n /**\n * Whether the field is marked required.\n *\n * Read from the host's `wr-form-field--required` modifier, not from the `*`:\n * that marker is `aria-hidden=\"true\"` decoration, and it is only rendered when\n * there is a label to hang it on — the modifier is set either way. Note what\n * this does NOT mean: the field forwards nothing to the projected control, so\n * `required` here is the field's own visual state. What announces the constraint\n * is the forms layer — a `required` rule writes the native `required` attribute\n * onto the control — and the two are set independently, so a field can carry the\n * `*` with no validator behind it.\n */\n async isRequired(): Promise<boolean> {\n return (await this.host()).hasClass('wr-form-field--required');\n }\n\n /**\n * Whether the label carries the `(optional)` marker.\n *\n * Read from the marker itself, because — unlike `required` — `optional` sets no\n * host modifier. It is also the marker a screen reader actually reads: this one\n * is not `aria-hidden`. `required` wins when both inputs are set, so a required\n * field is never optional here however it was bound.\n */\n async isOptional(): Promise<boolean> {\n return (await this.locatorForOptional(`${LABEL} .wr-form-field__optional`)()) !== null;\n }\n\n /**\n * The hint text, or `null` when none is showing.\n *\n * `null` does not mean the consumer set no hint: the error block takes the\n * hint's slot outright, so a field with a visible message has no hint in the DOM\n * at all. Note also that the hint carries no id and is NOT referenced by the\n * control's `aria-describedby` — see {@link getAnnouncedDescription}.\n */\n async getHint(): Promise<string | null> {\n const hint = await this.locatorForOptional(HINT)();\n return hint ? hint.text() : null;\n }\n\n /**\n * Whether the field is painting the error state.\n *\n * The host's `wr-form-field--invalid` modifier — what the user sees. The\n * screen-reader half lives on the control as `aria-invalid`, and the two are\n * separate elements rather than two readings of one: {@link isControlInvalid}.\n */\n async isInvalid(): Promise<boolean> {\n return (await this.host()).hasClass('wr-form-field--invalid');\n }\n\n /**\n * Every message the field is SHOWING, in DOM order — the consumer's markup\n * first, then whatever the field resolved for the keys that markup left over.\n *\n * Only THIS field's own error block is read — its direct child, holding its\n * direct children — so a `<wr-form-field>` nested in another one's control slot\n * contributes nothing to the outer field's answer. Without that anchoring the\n * outer field reports the inner one's copy as its own, which is the same class\n * of break as reading a sibling field's messages.\n */\n async getErrors(): Promise<WrFormFieldError[]> {\n const elements = await this.locatorForAll(VISIBLE)();\n\n return Promise.all(\n elements.map(async element => ({\n key: await element.getAttribute('data-key'),\n text: await element.text(),\n // The consumer's copy is its own element; a resolved message is a plain\n // `<div>` the field stamped out. That tag name is the whole of what the\n // DOM knows about provenance.\n source:\n (await element.getProperty<string>('tagName')).toLowerCase() === 'wr-form-error'\n ? ('projected' as const)\n : ('resolved' as const),\n }))\n );\n }\n\n /** The visible messages as text, in DOM order. */\n async getErrorTexts(): Promise<string[]> {\n return (await this.getErrors()).map(error => error.text);\n }\n\n /**\n * The visible message for one validator key.\n *\n * Throws rather than answering `null` when that key has no visible message: the\n * two reasons are worth failing on and the message names both — the key is not\n * in error at all, or its copy resolved to nothing.\n */\n async getErrorText(key: string): Promise<string> {\n const errors = await this.getErrors();\n const match = errors.find(error => error.key === key);\n if (match) return match.text;\n\n const shown = errors.map(error => error.key ?? '(keyless)');\n const detail =\n shown.length > 0\n ? `Showing: ${shown.join(', ')}.`\n : 'The field is showing nothing — it is either valid, or the control is still untouched and pristine.';\n\n throw new Error(\n `WrFormFieldHarness.getErrorText(${JSON.stringify(key)}): no visible message for that key. ${detail}`\n );\n }\n\n /**\n * The keys of the projected messages the field is holding BACK — copy that is in\n * the DOM but hidden because its key is not among the failing ones.\n *\n * This is the gating contract seen from outside, and the reason a spec must not\n * query `.wr-form-error` itself: the suppressed sentences sit right next to the\n * live one.\n */\n async getSuppressedErrorKeys(): Promise<string[]> {\n const hidden = await this.locatorForAll(SUPPRESSED)();\n const keys = await Promise.all(hidden.map(element => element.getAttribute('data-key')));\n return keys.filter((key): key is string => key !== null);\n }\n\n /**\n * Whether the field is announcing an error it has no words for: invalid, with an\n * error block that renders nothing.\n *\n * Worth a named check because it is invisible in review — the field still goes\n * red, and `aria-describedby` still points at the block, so a screen reader is\n * sent to an empty element. It happens when no link in the copy chain answers\n * the key (`[autoErrors]=\"false\"` with no markup, or a key nothing has copy for\n * — Signal Forms reports `minLength`, while the chain only knows Angular's\n * lowercase `minlength`).\n */\n async hasEmptyErrorBlock(): Promise<boolean> {\n if (!(await this.isInvalid())) return false;\n return (await this.getErrors()).every(error => error.text.length === 0);\n }\n\n /** The id the `<label for>` points at, or `null` when there is no label. */\n async getLabelFor(): Promise<string | null> {\n const label = await this.labelElement();\n return label ? label.getAttribute('for') : null;\n }\n\n /** The `id` the projected control actually answers to, or `null` when it has none. */\n async getControlId(): Promise<string | null> {\n return (await this.control()).getAttribute('id');\n }\n\n /**\n * Whether the `<label for>` resolves to an element inside THIS field's control\n * slot — the only thing that makes the field nameable.\n *\n * Scoped to the slot on purpose: ids are document-global, so a check that went\n * through `getElementById` would also pass for a label pointing at a DIFFERENT\n * field's control, and for a control that has been moved out of the field\n * entirely. The failure this catches is quiet — a projected control that does\n * not adopt the field's `controlId` (a bare `<input>` with no `[wrInput]`, say)\n * leaves a `for` naming nothing, the field renders exactly as it always did, and\n * nothing is labelled.\n */\n async isLabelLinkedToControl(): Promise<boolean> {\n const id = await this.getLabelFor();\n if (!id) return false;\n return (await this.locatorForOptional(`${SLOT} [id=\"${id}\"]`)()) !== null;\n }\n\n /**\n * The ids the projected control names in `aria-describedby`, in order.\n *\n * The field points this at the error block and nothing else — the hint has no\n * id — so an empty list while a hint is showing is correct rather than a bug.\n * That is not an oversight to fix from a harness either: `[wrInput]` derives\n * `aria-invalid` from the same signal, so describing the hint would announce\n * every hinted field as invalid.\n */\n async getDescribedByIds(): Promise<string[]> {\n const value = await (await this.control()).getAttribute('aria-describedby');\n return value ? value.split(/\\s+/).filter(Boolean) : [];\n }\n\n /**\n * What a screen reader reads as the control's description: the text of the\n * elements `aria-describedby` names, joined by a space. `null` when the control\n * describes nothing.\n *\n * `''` is a real answer and the interesting one — the reference resolves to an\n * element with no text in it, which is {@link hasEmptyErrorBlock}. Ids naming\n * something outside this field are skipped (a consumer's own description is out\n * of a field harness's reach); an id naming nothing anywhere throws, because a\n * dangling reference is the bug this method exists to surface.\n *\n * Suppressed copy is excluded explicitly rather than left to CSS: a browser drops\n * `display: none` subtrees from the accessibility tree, but a unit test loads no\n * stylesheet at all, so the referenced block's raw `textContent` runs every\n * held-back sentence together with the live one and this would answer with copy\n * nobody can read.\n */\n async getAnnouncedDescription(): Promise<string | null> {\n const ids = await this.getDescribedByIds();\n if (ids.length === 0) return null;\n\n const parts: string[] = [];\n for (const id of ids) {\n const inField = await this.locatorForOptional(`[id=\"${id}\"]`)();\n if (inField) {\n parts.push(await inField.text({ exclude: '.wr-form-field__error--hidden' }));\n continue;\n }\n if ((await this.documentRootLocatorFactory().locatorForOptional(`[id=\"${id}\"]`)()) === null) {\n throw new Error(\n `WrFormFieldHarness.getAnnouncedDescription(): the control's aria-describedby names \"${id}\", which is not ` +\n 'an element anywhere in the document — a screen reader is pointed at nothing.'\n );\n }\n }\n\n return parts.join(' ');\n }\n\n /**\n * Whether the control announces itself invalid.\n *\n * `aria-invalid` on the projected control, which is what assistive technology\n * is told — the field's own red styling is {@link isInvalid}. Both are here\n * because they live on different elements and travel in opposite directions: the\n * field renders the message, the control reads back that there is one. A field\n * that paints red while the control says nothing is a real (and silent) break.\n */\n async isControlInvalid(): Promise<boolean> {\n return (await (await this.control()).getAttribute('aria-invalid')) === 'true';\n }\n\n /** Move focus to the projected control. */\n async focusControl(): Promise<void> {\n return (await this.control()).focus();\n }\n\n /**\n * Blur the projected control, which is usually how a spec gets any message to\n * appear at all: a bound control marks itself touched on blur, and the field\n * renders nothing while the control is untouched AND pristine.\n */\n async blurControl(): Promise<void> {\n return (await this.control()).blur();\n }\n\n /** The rendered `<label>`, or `null` when `label` is empty. */\n private async labelElement(): Promise<TestElement | null> {\n return this.locatorForOptional(LABEL)();\n }\n\n /**\n * The control this field wraps.\n *\n * The element the label points at, when that resolves inside the slot — which\n * covers a control nested inside a component (`<wr-input-number>`'s own\n * `<input>` adopts the field's id, so the label names it, not the component\n * element). Otherwise the first native control in the slot, so a field with no\n * label still answers.\n */\n private async control(): Promise<TestElement> {\n const id = await this.getLabelFor();\n if (id) {\n const labelled = await this.locatorForOptional(`${SLOT} [id=\"${id}\"]`)();\n if (labelled) return labelled;\n }\n\n const native = await this.locatorForOptional(`${SLOT} input, ${SLOT} select, ${SLOT} textarea`)();\n if (native) return native;\n\n throw new Error(\n 'WrFormFieldHarness: this field wraps no control. Nothing in `.wr-form-field__control` answers to the ' +\n \"label's `for`, and there is no <input> / <select> / <textarea> to fall back to.\"\n );\n }\n\n /**\n * The content loader every `HarnessLoader` method on this class runs through,\n * scoped to the control slot.\n *\n * So `field.getHarness(WrInputHarness)` finds the control this field wraps and\n * not a sibling field's — the loader is per-instance by construction, which is\n * what a document-wide query for the same selector would lose.\n */\n protected override async getRootHarnessLoader(): Promise<HarnessLoader> {\n return this.locatorFactory.harnessLoaderFor(SLOT);\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 { ContentContainerComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrFormItemHarnessFilters } from './interfaces';\n\n/**\n * Test harness for `<wr-form-item>` — the layout-only field row.\n *\n * Deliberately a much smaller surface than {@link WrFormFieldHarness}, because\n * the component is a much smaller thing: it discovers no control, generates no\n * id, wires no `for` and no `aria-describedby`, and reads no validity. It paints\n * the error state it is TOLD to paint, via `[hasError]`, and everything else in\n * it belongs to the consumer — including the `<label>`, which is named here\n * rather than linked (nothing wires it; the consumer's own `for` is the only\n * thing that can).\n *\n * The one behavioural trap it is worth a harness for: a `<wr-form-error>` inside\n * a form-item renders UNCONDITIONALLY. `key` has no effect at all — the gating\n * lives in the surrounding `<wr-form-field>`, and with no field to ask, every\n * message decides it is the one to show. Markup moved from a field into an item\n * therefore starts printing every sentence at once, and {@link getErrorTexts}\n * is what says so.\n *\n * @example\n * ```ts\n * const item = await loader.getHarness(WrFormItemHarness.with({ label: 'Email' }));\n *\n * expect(await item.isInvalid()).toBe(true);\n * expect(await item.getErrorTexts()).toEqual(['Invalid email']);\n * await (await item.getHarness(WrInputHarness)).setValue('ada@example.test');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrFormItemHarness extends ContentContainerComponentHarness {\n static hostSelector = 'wr-form-item';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrFormItemHarnessFilters = {}): HarnessPredicate<WrFormItemHarness> {\n return new HarnessPredicate(WrFormItemHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('invalid', options.invalid, async (harness, invalid) => (await harness.isInvalid()) === invalid)\n .addOption('errorText', options.errorText, async (harness, text) => {\n for (const shown of await harness.getErrorTexts()) {\n if (await HarnessPredicate.stringMatches(shown, text)) return true;\n }\n return false;\n });\n }\n\n /**\n * The text of the item's own `<label>` child, or `null` when it has none.\n *\n * Direct child only — that is what the item styles (`> label` is what turns red\n * in the error state), so a label nested deeper is the consumer's own business\n * and answering with it would describe something the item never touches.\n */\n async getLabel(): Promise<string | null> {\n const label = await this.locatorForOptional(':scope > label')();\n return label ? label.text() : null;\n }\n\n /**\n * Whether the item is painting the error state.\n *\n * The `wr-form-item--error` modifier, which is driven entirely by the\n * consumer's `[hasError]` — the item runs no validity check of its own, so this\n * answers what it was told, not what the control thinks.\n */\n async isInvalid(): Promise<boolean> {\n return (await this.host()).hasClass('wr-form-item--error');\n }\n\n /**\n * Every `<wr-form-error>` in the item, as text, in DOM order.\n *\n * All of them: nothing is gated here, `key` or no `key`. Nothing is ever\n * hidden either, so there is no suppressed-message counterpart to this — see\n * the class docs.\n */\n async getErrorTexts(): Promise<string[]> {\n const errors = await this.locatorForAll('wr-form-error')();\n return Promise.all(errors.map(error => error.text()));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;AAAA;;;;;AAKG;AAWH;;;;;;;AAOG;AACH,MAAM,OAAO,GAAG,yDAAyD;AACzE,MAAM,OAAO,GAAG,CAAA,EAAG,OAAO,qCAAqC;AAC/D,MAAM,UAAU,GAAG,CAAA,EAAG,OAAO,+BAA+B;AAE5D;AACA,MAAM,IAAI,GAAG,kCAAkC;AAE/C;AACA,MAAM,KAAK,GAAG,gCAAgC;AAC9C,MAAM,IAAI,GAAG,+BAA+B;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CG;AACG,MAAO,kBAAmB,SAAQ,gCAAgC,CAAA;AACtE,IAAA,OAAO,YAAY,GAAG,eAAe;;AAGrC,IAAA,OAAO,IAAI,CAAC,OAAA,GAAqC,EAAE,EAAA;AACjD,QAAA,OAAO,IAAI,gBAAgB,CAAC,kBAAkB,EAAE,OAAO;aACpD,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,KAAK,KAAK,gBAAgB,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC;aAC/G,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,KAAK,gBAAgB,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC;aAC1G,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC,MAAM,OAAO,CAAC,UAAU,EAAE,MAAM,QAAQ;aAC9G,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK,CAAC,MAAM,OAAO,CAAC,SAAS,EAAE,MAAM,OAAO;AACzG,aAAA,SAAS,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,OAAO,EAAE,IAAI,KAAI;YACjE,KAAK,MAAM,KAAK,IAAI,MAAM,OAAO,CAAC,aAAa,EAAE,EAAE;gBACjD,IAAI,MAAM,gBAAgB,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC;AAAE,oBAAA,OAAO,IAAI;YACpE;AACA,YAAA,OAAO,KAAK;AACd,QAAA,CAAC,CAAC;IACN;AAEA;;;;;;;;;AASG;AACH,IAAA,MAAM,QAAQ,GAAA;AACZ,QAAA,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE;AACvC,QAAA,OAAO,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,oDAAoD,EAAE,CAAC,GAAG,IAAI;IACrG;AAEA;;;;;;;;;;;AAWG;AACH,IAAA,MAAM,UAAU,GAAA;AACd,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,yBAAyB,CAAC;IAChE;AAEA;;;;;;;AAOG;AACH,IAAA,MAAM,UAAU,GAAA;AACd,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,kBAAkB,CAAC,CAAA,EAAG,KAAK,CAAA,yBAAA,CAA2B,CAAC,EAAE,MAAM,IAAI;IACxF;AAEA;;;;;;;AAOG;AACH,IAAA,MAAM,OAAO,GAAA;QACX,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE;AAClD,QAAA,OAAO,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI;IAClC;AAEA;;;;;;AAMG;AACH,IAAA,MAAM,SAAS,GAAA;AACb,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,wBAAwB,CAAC;IAC/D;AAEA;;;;;;;;;AASG;AACH,IAAA,MAAM,SAAS,GAAA;QACb,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE;AAEpD,QAAA,OAAO,OAAO,CAAC,GAAG,CAChB,QAAQ,CAAC,GAAG,CAAC,OAAM,OAAO,MAAK;AAC7B,YAAA,GAAG,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC;AAC3C,YAAA,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,EAAE;;;;AAI1B,YAAA,MAAM,EACJ,CAAC,MAAM,OAAO,CAAC,WAAW,CAAS,SAAS,CAAC,EAAE,WAAW,EAAE,KAAK;AAC/D,kBAAG;AACH,kBAAG,UAAoB;SAC5B,CAAC,CAAC,CACJ;IACH;;AAGA,IAAA,MAAM,aAAa,GAAA;AACjB,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,EAAE,GAAG,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC;IAC1D;AAEA;;;;;;AAMG;IACH,MAAM,YAAY,CAAC,GAAW,EAAA;AAC5B,QAAA,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE;AACrC,QAAA,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC;AACrD,QAAA,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC,IAAI;AAE5B,QAAA,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,IAAI,WAAW,CAAC;AAC3D,QAAA,MAAM,MAAM,GACV,KAAK,CAAC,MAAM,GAAG;cACX,YAAY,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAA;cAC5B,oGAAoG;AAE1G,QAAA,MAAM,IAAI,KAAK,CACb,CAAA,gCAAA,EAAmC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA,oCAAA,EAAuC,MAAM,CAAA,CAAE,CACtG;IACH;AAEA;;;;;;;AAOG;AACH,IAAA,MAAM,sBAAsB,GAAA;QAC1B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE;QACrD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC;AACvF,QAAA,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,KAAoB,GAAG,KAAK,IAAI,CAAC;IAC1D;AAEA;;;;;;;;;;AAUG;AACH,IAAA,MAAM,kBAAkB,GAAA;AACtB,QAAA,IAAI,EAAE,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;AAAE,YAAA,OAAO,KAAK;QAC3C,OAAO,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,EAAE,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC;IACzE;;AAGA,IAAA,MAAM,WAAW,GAAA;AACf,QAAA,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE;AACvC,QAAA,OAAO,KAAK,GAAG,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI;IACjD;;AAGA,IAAA,MAAM,YAAY,GAAA;AAChB,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC;IAClD;AAEA;;;;;;;;;;;AAWG;AACH,IAAA,MAAM,sBAAsB,GAAA;AAC1B,QAAA,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE;AACnC,QAAA,IAAI,CAAC,EAAE;AAAE,YAAA,OAAO,KAAK;AACrB,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,kBAAkB,CAAC,CAAA,EAAG,IAAI,CAAA,MAAA,EAAS,EAAE,IAAI,CAAC,EAAE,MAAM,IAAI;IAC3E;AAEA;;;;;;;;AAQG;AACH,IAAA,MAAM,iBAAiB,GAAA;AACrB,QAAA,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,kBAAkB,CAAC;AAC3E,QAAA,OAAO,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE;IACxD;AAEA;;;;;;;;;;;;;;;;AAgBG;AACH,IAAA,MAAM,uBAAuB,GAAA;AAC3B,QAAA,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE;AAC1C,QAAA,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;AAAE,YAAA,OAAO,IAAI;QAEjC,MAAM,KAAK,GAAa,EAAE;AAC1B,QAAA,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE;AACpB,YAAA,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,CAAA,KAAA,EAAQ,EAAE,CAAA,EAAA,CAAI,CAAC,EAAE;YAC/D,IAAI,OAAO,EAAE;AACX,gBAAA,KAAK,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC,CAAC;gBAC5E;YACF;AACA,YAAA,IAAI,CAAC,MAAM,IAAI,CAAC,0BAA0B,EAAE,CAAC,kBAAkB,CAAC,CAAA,KAAA,EAAQ,EAAE,IAAI,CAAC,EAAE,MAAM,IAAI,EAAE;AAC3F,gBAAA,MAAM,IAAI,KAAK,CACb,CAAA,oFAAA,EAAuF,EAAE,CAAA,gBAAA,CAAkB;AACzG,oBAAA,8EAA8E,CACjF;YACH;QACF;AAEA,QAAA,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;IACxB;AAEA;;;;;;;;AAQG;AACH,IAAA,MAAM,gBAAgB,GAAA;AACpB,QAAA,OAAO,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,cAAc,CAAC,MAAM,MAAM;IAC/E;;AAGA,IAAA,MAAM,YAAY,GAAA;QAChB,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE;IACvC;AAEA;;;;AAIG;AACH,IAAA,MAAM,WAAW,GAAA;QACf,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE;IACtC;;AAGQ,IAAA,MAAM,YAAY,GAAA;AACxB,QAAA,OAAO,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE;IACzC;AAEA;;;;;;;;AAQG;AACK,IAAA,MAAM,OAAO,GAAA;AACnB,QAAA,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE;QACnC,IAAI,EAAE,EAAE;AACN,YAAA,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,CAAA,EAAG,IAAI,CAAA,MAAA,EAAS,EAAE,CAAA,EAAA,CAAI,CAAC,EAAE;AACxE,YAAA,IAAI,QAAQ;AAAE,gBAAA,OAAO,QAAQ;QAC/B;AAEA,QAAA,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,CAAA,EAAG,IAAI,CAAA,QAAA,EAAW,IAAI,CAAA,SAAA,EAAY,IAAI,CAAA,SAAA,CAAW,CAAC,EAAE;AACjG,QAAA,IAAI,MAAM;AAAE,YAAA,OAAO,MAAM;QAEzB,MAAM,IAAI,KAAK,CACb,uGAAuG;AACrG,YAAA,iFAAiF,CACpF;IACH;AAEA;;;;;;;AAOG;AACgB,IAAA,MAAM,oBAAoB,GAAA;QAC3C,OAAO,IAAI,CAAC,cAAc,CAAC,gBAAgB,CAAC,IAAI,CAAC;IACnD;;;ACjZF;;;;;AAKG;AAMH;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BG;AACG,MAAO,iBAAkB,SAAQ,gCAAgC,CAAA;AACrE,IAAA,OAAO,YAAY,GAAG,cAAc;;AAGpC,IAAA,OAAO,IAAI,CAAC,OAAA,GAAoC,EAAE,EAAA;AAChD,QAAA,OAAO,IAAI,gBAAgB,CAAC,iBAAiB,EAAE,OAAO;aACnD,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,KAAK,KAAK,gBAAgB,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,KAAK,CAAC;aAC/G,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK,CAAC,MAAM,OAAO,CAAC,SAAS,EAAE,MAAM,OAAO;AACzG,aAAA,SAAS,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,EAAE,OAAO,OAAO,EAAE,IAAI,KAAI;YACjE,KAAK,MAAM,KAAK,IAAI,MAAM,OAAO,CAAC,aAAa,EAAE,EAAE;gBACjD,IAAI,MAAM,gBAAgB,CAAC,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC;AAAE,oBAAA,OAAO,IAAI;YACpE;AACA,YAAA,OAAO,KAAK;AACd,QAAA,CAAC,CAAC;IACN;AAEA;;;;;;AAMG;AACH,IAAA,MAAM,QAAQ,GAAA;QACZ,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,EAAE;AAC/D,QAAA,OAAO,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,GAAG,IAAI;IACpC;AAEA;;;;;;AAMG;AACH,IAAA,MAAM,SAAS,GAAA;AACb,QAAA,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,qBAAqB,CAAC;IAC5D;AAEA;;;;;;AAMG;AACH,IAAA,MAAM,aAAa,GAAA;QACjB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,EAAE;AAC1D,QAAA,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACvD;;;ACzFF;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"ngwr-form-testing.mjs","sources":["../../../projects/lib/form/testing/wr-form-field-harness.ts","../../../projects/lib/form/testing/wr-form-item-harness.ts","../../../projects/lib/form/testing/ngwr-form-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 {\n ContentContainerComponentHarness,\n type HarnessLoader,\n HarnessPredicate,\n type TestElement,\n} from '@angular/cdk/testing';\n\nimport type { WrFormFieldError, WrFormFieldHarnessFilters } from './interfaces';\n\n/**\n * The messages this field is showing, or the ones it is holding back.\n *\n * Anchored with `:scope >` because the field's own parts are all direct children\n * of its host: a `<wr-form-field>` nested in another one's control slot otherwise\n * has its error block matched by the OUTER field's query too, and the outer\n * answers with a sentence that is not its own.\n */\nconst MESSAGE = ':scope > .wr-form-field__errors > .wr-form-field__error';\nconst VISIBLE = `${MESSAGE}:not(.wr-form-field__error--hidden)`;\nconst SUPPRESSED = `${MESSAGE}.wr-form-field__error--hidden`;\n\n/** The control slot — every native control lookup is scoped through this. */\nconst SLOT = ':scope > .wr-form-field__control';\n\n/** The label and the hint, anchored for the same reason as {@link MESSAGE}. */\nconst LABEL = ':scope > .wr-form-field__label';\nconst HINT = ':scope > .wr-form-field__hint';\n\n/**\n * Test harness for `<wr-form-field>` — the label + control + hint + validation\n * copy block.\n *\n * The field's real job is the COPY CONTRACT, and this harness is built around it.\n * A message reaches the DOM one of two ways, and they are distinguishable there:\n *\n * - a `<wr-form-error key=\"…\">` the consumer wrote, which renders only while that\n * key is failing — the others stay in the DOM carrying\n * `wr-form-field__error--hidden`, which is why {@link getErrors} filters and\n * {@link getSuppressedErrorKeys} exists. A raw `.wr-form-error` query answers\n * with the held-back copy too, and reads green on the wrong sentence.\n * - a message the field RESOLVED for an error no markup claimed:\n * `provideWrFormErrors()`, then the `ngwr/i18n` `validation.*` catalog, then a\n * built-in English sentence. All three render the identical `<div>`, so\n * `source` is `'resolved'` for every one of them — the DOM genuinely cannot say\n * which link answered, and this harness does not pretend otherwise.\n *\n * Nothing renders at all until the projected control is touched or dirty, so a\n * spec has to reach that gate first: {@link blurControl} is the shortest way\n * (blur is what marks a bound control touched), and typing through the control's\n * own harness is the other.\n *\n * The field publishes no `aria-label` anywhere. Its `<label for>` pointing at the\n * projected control IS the naming story, and a control that does not adopt the\n * id leaves the field silently unlabelled — {@link isLabelLinkedToControl} is\n * what catches that. The other half is the reverse wiring: the control reads back\n * `aria-describedby` and `aria-invalid`, which is the only reason a screen reader\n * hears the message ({@link getAnnouncedDescription}).\n *\n * Harnesses loaded through this one resolve INSIDE the control slot, so the\n * control's own harness composes without a selector:\n *\n * @example\n * ```ts\n * const email = await loader.getHarness(WrFormFieldHarness.with({ label: 'Email' }));\n *\n * await (await email.getHarness(WrInputHarness)).setValue('nope');\n * await email.blurControl();\n *\n * expect(await email.getErrorTexts()).toEqual([\"That isn't a valid email.\"]);\n * expect(await email.getAnnouncedDescription()).toBe(\"That isn't a valid email.\");\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrFormFieldHarness extends ContentContainerComponentHarness {\n static hostSelector = 'wr-form-field';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrFormFieldHarnessFilters = {}): HarnessPredicate<WrFormFieldHarness> {\n return new HarnessPredicate(WrFormFieldHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('hint', options.hint, (harness, hint) => HarnessPredicate.stringMatches(harness.getHint(), hint))\n .addOption('required', options.required, async (harness, required) => (await harness.isRequired()) === required)\n .addOption('invalid', options.invalid, async (harness, invalid) => (await harness.isInvalid()) === invalid)\n .addOption('errorText', options.errorText, async (harness, text) => {\n for (const shown of await harness.getErrorTexts()) {\n if (await HarnessPredicate.stringMatches(shown, text)) return true;\n }\n return false;\n });\n }\n\n /**\n * The label text, or `null` when the field renders no label.\n *\n * The `*` and `(optional)` markers ride INSIDE the `<label>`, so they are\n * excluded here — otherwise this answers `'Email *'` and every equality\n * assertion has to know which marker the field happens to carry.\n *\n * `null` is not \"the consumer forgot\": `label=\"\"` renders no `<label>` element\n * at all, which also means there is nothing naming the control.\n */\n async getLabel(): Promise<string | null> {\n const label = await this.labelElement();\n return label ? label.text({ exclude: '.wr-form-field__required, .wr-form-field__optional' }) : null;\n }\n\n /**\n * Whether the field is marked required.\n *\n * Read from the host's `wr-form-field--required` modifier, not from the `*`:\n * that marker is `aria-hidden=\"true\"` decoration, and it is only rendered when\n * there is a label to hang it on — the modifier is set either way. Note what\n * this does NOT mean: the field forwards nothing to the projected control, so\n * `required` here is the field's own visual state. What announces the constraint\n * is the forms layer — a `required` rule writes the native `required` attribute\n * onto the control — and the two are set independently, so a field can carry the\n * `*` with no validator behind it.\n */\n async isRequired(): Promise<boolean> {\n return (await this.host()).hasClass('wr-form-field--required');\n }\n\n /**\n * Whether the label carries the `(optional)` marker.\n *\n * Read from the marker itself, because — unlike `required` — `optional` sets no\n * host modifier. It is also the marker a screen reader actually reads: this one\n * is not `aria-hidden`. `required` wins when both inputs are set, so a required\n * field is never optional here however it was bound.\n */\n async isOptional(): Promise<boolean> {\n return (await this.locatorForOptional(`${LABEL} .wr-form-field__optional`)()) !== null;\n }\n\n /**\n * The hint text, or `null` when none is showing.\n *\n * `null` does not mean the consumer set no hint: the error block takes the\n * hint's slot outright, so a field with a visible message has no hint in the DOM\n * at all — which is also why the hint's id stops being published at the same\n * moment. Whether the CONTROL announces it is a separate question, and one this\n * harness answers through {@link getAnnouncedDescription}.\n */\n async getHint(): Promise<string | null> {\n const hint = await this.locatorForOptional(HINT)();\n return hint ? hint.text() : null;\n }\n\n /**\n * Whether the field is painting the error state.\n *\n * The host's `wr-form-field--invalid` modifier — what the user sees. The\n * screen-reader half lives on the control as `aria-invalid`, and the two are\n * separate elements rather than two readings of one: {@link isControlInvalid}.\n */\n async isInvalid(): Promise<boolean> {\n return (await this.host()).hasClass('wr-form-field--invalid');\n }\n\n /**\n * Every message the field is SHOWING, in DOM order — the consumer's markup\n * first, then whatever the field resolved for the keys that markup left over.\n *\n * Only THIS field's own error block is read — its direct child, holding its\n * direct children — so a `<wr-form-field>` nested in another one's control slot\n * contributes nothing to the outer field's answer. Without that anchoring the\n * outer field reports the inner one's copy as its own, which is the same class\n * of break as reading a sibling field's messages.\n */\n async getErrors(): Promise<WrFormFieldError[]> {\n const elements = await this.locatorForAll(VISIBLE)();\n\n return Promise.all(\n elements.map(async element => ({\n key: await element.getAttribute('data-key'),\n text: await element.text(),\n // The consumer's copy is its own element; a resolved message is a plain\n // `<div>` the field stamped out. That tag name is the whole of what the\n // DOM knows about provenance.\n source:\n (await element.getProperty<string>('tagName')).toLowerCase() === 'wr-form-error'\n ? ('projected' as const)\n : ('resolved' as const),\n }))\n );\n }\n\n /** The visible messages as text, in DOM order. */\n async getErrorTexts(): Promise<string[]> {\n return (await this.getErrors()).map(error => error.text);\n }\n\n /**\n * The visible message for one validator key.\n *\n * Throws rather than answering `null` when that key has no visible message: the\n * two reasons are worth failing on and the message names both — the key is not\n * in error at all, or its copy resolved to nothing.\n */\n async getErrorText(key: string): Promise<string> {\n const errors = await this.getErrors();\n const match = errors.find(error => error.key === key);\n if (match) return match.text;\n\n const shown = errors.map(error => error.key ?? '(keyless)');\n const detail =\n shown.length > 0\n ? `Showing: ${shown.join(', ')}.`\n : 'The field is showing nothing — it is either valid, or the control is still untouched and pristine.';\n\n throw new Error(\n `WrFormFieldHarness.getErrorText(${JSON.stringify(key)}): no visible message for that key. ${detail}`\n );\n }\n\n /**\n * The keys of the projected messages the field is holding BACK — copy that is in\n * the DOM but hidden because its key is not among the failing ones.\n *\n * This is the gating contract seen from outside, and the reason a spec must not\n * query `.wr-form-error` itself: the suppressed sentences sit right next to the\n * live one.\n */\n async getSuppressedErrorKeys(): Promise<string[]> {\n const hidden = await this.locatorForAll(SUPPRESSED)();\n const keys = await Promise.all(hidden.map(element => element.getAttribute('data-key')));\n return keys.filter((key): key is string => key !== null);\n }\n\n /**\n * Whether the field is announcing an error it has no words for: invalid, with an\n * error block that renders nothing.\n *\n * Worth a named check because it is invisible in review — the field still goes\n * red, and `aria-describedby` still points at the block, so a screen reader is\n * sent to an empty element. It happens when no link in the copy chain answers\n * the key (`[autoErrors]=\"false\"` with no markup, or a key nothing has copy for\n * — Signal Forms reports `minLength`, while the chain only knows Angular's\n * lowercase `minlength`).\n */\n async hasEmptyErrorBlock(): Promise<boolean> {\n if (!(await this.isInvalid())) return false;\n return (await this.getErrors()).every(error => error.text.length === 0);\n }\n\n /** The id the `<label for>` points at, or `null` when there is no label. */\n async getLabelFor(): Promise<string | null> {\n const label = await this.labelElement();\n return label ? label.getAttribute('for') : null;\n }\n\n /** The `id` the projected control actually answers to, or `null` when it has none. */\n async getControlId(): Promise<string | null> {\n return (await this.control()).getAttribute('id');\n }\n\n /**\n * Whether the `<label for>` resolves to an element inside THIS field's control\n * slot — the only thing that makes the field nameable.\n *\n * Scoped to the slot on purpose: ids are document-global, so a check that went\n * through `getElementById` would also pass for a label pointing at a DIFFERENT\n * field's control, and for a control that has been moved out of the field\n * entirely. The failure this catches is quiet — a projected control that does\n * not adopt the field's `controlId` (a bare `<input>` with no `[wrInput]`, say)\n * leaves a `for` naming nothing, the field renders exactly as it always did, and\n * nothing is labelled.\n */\n async isLabelLinkedToControl(): Promise<boolean> {\n const id = await this.getLabelFor();\n if (!id) return false;\n return (await this.locatorForOptional(`${SLOT} [id=\"${id}\"]`)()) !== null;\n }\n\n /**\n * The ids the projected control names in `aria-describedby`, in order.\n *\n * Which id that is depends on the CONTROL, not on the field, and the answer is\n * worth knowing before reading a result. A control that composes the pair with\n * `useFormFieldAria()` names the error block while one is showing and the hint\n * otherwise. A control that reads the field's `describedBy` on its own — the\n * `[wrInput]` directive, `wr-segmented` and `wr-slider` — names the error block\n * and nothing else, so an empty list beside a visible hint is that control's\n * answer rather than a broken field.\n *\n * The two are separate because folding the hint into the one signal announced\n * every hinted field as invalid: those three derive `aria-invalid` from it.\n */\n async getDescribedByIds(): Promise<string[]> {\n const value = await (await this.control()).getAttribute('aria-describedby');\n return value ? value.split(/\\s+/).filter(Boolean) : [];\n }\n\n /**\n * What a screen reader reads as the control's description: the text of the\n * elements `aria-describedby` names, joined by a space. `null` when the control\n * describes nothing.\n *\n * `''` is a real answer and the interesting one — the reference resolves to an\n * element with no text in it, which is {@link hasEmptyErrorBlock}. Ids naming\n * something outside this field are skipped (a consumer's own description is out\n * of a field harness's reach); an id naming nothing anywhere throws, because a\n * dangling reference is the bug this method exists to surface.\n *\n * Suppressed copy is excluded explicitly rather than left to CSS: a browser drops\n * `display: none` subtrees from the accessibility tree, but a unit test loads no\n * stylesheet at all, so the referenced block's raw `textContent` runs every\n * held-back sentence together with the live one and this would answer with copy\n * nobody can read.\n */\n async getAnnouncedDescription(): Promise<string | null> {\n const ids = await this.getDescribedByIds();\n if (ids.length === 0) return null;\n\n const parts: string[] = [];\n for (const id of ids) {\n const inField = await this.locatorForOptional(`[id=\"${id}\"]`)();\n if (inField) {\n parts.push(await inField.text({ exclude: '.wr-form-field__error--hidden' }));\n continue;\n }\n if ((await this.documentRootLocatorFactory().locatorForOptional(`[id=\"${id}\"]`)()) === null) {\n throw new Error(\n `WrFormFieldHarness.getAnnouncedDescription(): the control's aria-describedby names \"${id}\", which is not ` +\n 'an element anywhere in the document — a screen reader is pointed at nothing.'\n );\n }\n }\n\n return parts.join(' ');\n }\n\n /**\n * Whether the control announces itself invalid.\n *\n * `aria-invalid` on the projected control, which is what assistive technology\n * is told — the field's own red styling is {@link isInvalid}. Both are here\n * because they live on different elements and travel in opposite directions: the\n * field renders the message, the control reads back that there is one. A field\n * that paints red while the control says nothing is a real (and silent) break.\n */\n async isControlInvalid(): Promise<boolean> {\n return (await (await this.control()).getAttribute('aria-invalid')) === 'true';\n }\n\n /** Move focus to the projected control. */\n async focusControl(): Promise<void> {\n return (await this.control()).focus();\n }\n\n /**\n * Blur the projected control, which is usually how a spec gets any message to\n * appear at all: a bound control marks itself touched on blur, and the field\n * renders nothing while the control is untouched AND pristine.\n */\n async blurControl(): Promise<void> {\n return (await this.control()).blur();\n }\n\n /** The rendered `<label>`, or `null` when `label` is empty. */\n private async labelElement(): Promise<TestElement | null> {\n return this.locatorForOptional(LABEL)();\n }\n\n /**\n * The control this field wraps.\n *\n * The element the label points at, when that resolves inside the slot — which\n * covers a control nested inside a component (`<wr-input-number>`'s own\n * `<input>` adopts the field's id, so the label names it, not the component\n * element). Otherwise the first native control in the slot, so a field with no\n * label still answers.\n */\n private async control(): Promise<TestElement> {\n const id = await this.getLabelFor();\n if (id) {\n const labelled = await this.locatorForOptional(`${SLOT} [id=\"${id}\"]`)();\n if (labelled) return labelled;\n }\n\n const native = await this.locatorForOptional(`${SLOT} input, ${SLOT} select, ${SLOT} textarea`)();\n if (native) return native;\n\n throw new Error(\n 'WrFormFieldHarness: this field wraps no control. Nothing in `.wr-form-field__control` answers to the ' +\n \"label's `for`, and there is no <input> / <select> / <textarea> to fall back to.\"\n );\n }\n\n /**\n * The content loader every `HarnessLoader` method on this class runs through,\n * scoped to the control slot.\n *\n * So `field.getHarness(WrInputHarness)` finds the control this field wraps and\n * not a sibling field's — the loader is per-instance by construction, which is\n * what a document-wide query for the same selector would lose.\n */\n protected override async getRootHarnessLoader(): Promise<HarnessLoader> {\n return this.locatorFactory.harnessLoaderFor(SLOT);\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 { ContentContainerComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrFormItemHarnessFilters } from './interfaces';\n\n/**\n * Test harness for `<wr-form-item>` — the layout-only field row.\n *\n * Deliberately a much smaller surface than {@link WrFormFieldHarness}, because\n * the component is a much smaller thing: it discovers no control, generates no\n * id, wires no `for` and no `aria-describedby`, and reads no validity. It paints\n * the error state it is TOLD to paint, via `[hasError]`, and everything else in\n * it belongs to the consumer — including the `<label>`, which is named here\n * rather than linked (nothing wires it; the consumer's own `for` is the only\n * thing that can).\n *\n * The one behavioural trap it is worth a harness for: a `<wr-form-error>` inside\n * a form-item renders UNCONDITIONALLY. `key` has no effect at all — the gating\n * lives in the surrounding `<wr-form-field>`, and with no field to ask, every\n * message decides it is the one to show. Markup moved from a field into an item\n * therefore starts printing every sentence at once, and {@link getErrorTexts}\n * is what says so.\n *\n * @example\n * ```ts\n * const item = await loader.getHarness(WrFormItemHarness.with({ label: 'Email' }));\n *\n * expect(await item.isInvalid()).toBe(true);\n * expect(await item.getErrorTexts()).toEqual(['Invalid email']);\n * await (await item.getHarness(WrInputHarness)).setValue('ada@example.test');\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrFormItemHarness extends ContentContainerComponentHarness {\n static hostSelector = 'wr-form-item';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrFormItemHarnessFilters = {}): HarnessPredicate<WrFormItemHarness> {\n return new HarnessPredicate(WrFormItemHarness, options)\n .addOption('label', options.label, (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label))\n .addOption('invalid', options.invalid, async (harness, invalid) => (await harness.isInvalid()) === invalid)\n .addOption('errorText', options.errorText, async (harness, text) => {\n for (const shown of await harness.getErrorTexts()) {\n if (await HarnessPredicate.stringMatches(shown, text)) return true;\n }\n return false;\n });\n }\n\n /**\n * The text of the item's own `<label>` child, or `null` when it has none.\n *\n * Direct child only — that is what the item styles (`> label` is what turns red\n * in the error state), so a label nested deeper is the consumer's own business\n * and answering with it would describe something the item never touches.\n */\n async getLabel(): Promise<string | null> {\n const label = await this.locatorForOptional(':scope > label')();\n return label ? label.text() : null;\n }\n\n /**\n * Whether the item is painting the error state.\n *\n * The `wr-form-item--error` modifier, which is driven entirely by the\n * consumer's `[hasError]` — the item runs no validity check of its own, so this\n * answers what it was told, not what the control thinks.\n */\n async isInvalid(): Promise<boolean> {\n return (await this.host()).hasClass('wr-form-item--error');\n }\n\n /**\n * Every `<wr-form-error>` in the item, as text, in DOM order.\n *\n * All of them: nothing is gated here, `key` or no `key`. Nothing is ever\n * hidden either, so there is no suppressed-message counterpart to this — see\n * the class docs.\n */\n async getErrorTexts(): Promise<string[]> {\n const errors = await this.locatorForAll('wr-form-error')();\n return Promise.all(errors.map(error => error.text()));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|