ngwr 13.0.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -6
- package/color-picker/styles/_index.scss +43 -5
- package/fesm2022/ngwr-alert.mjs +5 -5
- package/fesm2022/ngwr-alert.mjs.map +1 -1
- package/fesm2022/ngwr-back-top.mjs +11 -1
- package/fesm2022/ngwr-back-top.mjs.map +1 -1
- package/fesm2022/ngwr-blur-text.mjs +13 -2
- package/fesm2022/ngwr-blur-text.mjs.map +1 -1
- package/fesm2022/ngwr-border-glow.mjs +12 -3
- package/fesm2022/ngwr-border-glow.mjs.map +1 -1
- package/fesm2022/ngwr-breadcrumbs.mjs +14 -3
- package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
- package/fesm2022/ngwr-button.mjs +4 -4
- package/fesm2022/ngwr-button.mjs.map +1 -1
- package/fesm2022/ngwr-calendar-testing.mjs +15 -0
- package/fesm2022/ngwr-calendar-testing.mjs.map +1 -1
- package/fesm2022/ngwr-calendar.mjs +51 -5
- package/fesm2022/ngwr-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-circular-text.mjs +20 -1
- package/fesm2022/ngwr-circular-text.mjs.map +1 -1
- package/fesm2022/ngwr-collapse.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker-testing.mjs +23 -7
- package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker.mjs +89 -10
- package/fesm2022/ngwr-color-picker.mjs.map +1 -1
- package/fesm2022/ngwr-command-palette.mjs +27 -3
- package/fesm2022/ngwr-command-palette.mjs.map +1 -1
- package/fesm2022/ngwr-context-menu.mjs +13 -3
- package/fesm2022/ngwr-context-menu.mjs.map +1 -1
- package/fesm2022/ngwr-date-adapters-fns.mjs +101 -33
- package/fesm2022/ngwr-date-adapters-fns.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker-testing.mjs +10 -2
- package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker.mjs +103 -13
- package/fesm2022/ngwr-date-picker.mjs.map +1 -1
- package/fesm2022/ngwr-date.mjs +480 -79
- package/fesm2022/ngwr-date.mjs.map +1 -1
- package/fesm2022/ngwr-dialog.mjs +22 -16
- package/fesm2022/ngwr-dialog.mjs.map +1 -1
- package/fesm2022/ngwr-directives.mjs +5 -2
- package/fesm2022/ngwr-directives.mjs.map +1 -1
- package/fesm2022/ngwr-drag-drop.mjs +30 -2
- package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
- package/fesm2022/ngwr-drawer.mjs +24 -18
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown.mjs +8 -3
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar.mjs +1 -1
- package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-file-upload.mjs +52 -12
- package/fesm2022/ngwr-file-upload.mjs.map +1 -1
- package/fesm2022/ngwr-fuzzy-text.mjs +21 -4
- package/fesm2022/ngwr-fuzzy-text.mjs.map +1 -1
- package/fesm2022/ngwr-gradient-text.mjs +9 -2
- package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-en.mjs +63 -1
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +28 -1
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-i18n.mjs +150 -22
- package/fesm2022/ngwr-i18n.mjs.map +1 -1
- package/fesm2022/ngwr-icon-adapters-feather.mjs +11 -0
- package/fesm2022/ngwr-icon-adapters-feather.mjs.map +1 -1
- package/fesm2022/ngwr-icon-adapters-lucide.mjs +22 -2
- package/fesm2022/ngwr-icon-adapters-lucide.mjs.map +1 -1
- package/fesm2022/ngwr-icon.mjs +474 -37
- package/fesm2022/ngwr-icon.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +3 -3
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-input-otp.mjs +21 -3
- package/fesm2022/ngwr-input-otp.mjs.map +1 -1
- package/fesm2022/ngwr-input.mjs +14 -7
- package/fesm2022/ngwr-input.mjs.map +1 -1
- package/fesm2022/ngwr-knob-testing.mjs.map +1 -1
- package/fesm2022/ngwr-knob.mjs +20 -2
- package/fesm2022/ngwr-knob.mjs.map +1 -1
- package/fesm2022/ngwr-loading-bar-router.mjs +73 -0
- package/fesm2022/ngwr-loading-bar-router.mjs.map +1 -0
- package/fesm2022/ngwr-loading-bar.mjs +21 -32
- package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
- package/fesm2022/ngwr-markdown.mjs +63 -11
- package/fesm2022/ngwr-markdown.mjs.map +1 -1
- package/fesm2022/ngwr-mention.mjs +104 -14
- package/fesm2022/ngwr-mention.mjs.map +1 -1
- package/fesm2022/ngwr-overlay.mjs +36 -7
- package/fesm2022/ngwr-overlay.mjs.map +1 -1
- package/fesm2022/ngwr-pagination-testing.mjs +11 -10
- package/fesm2022/ngwr-pagination-testing.mjs.map +1 -1
- package/fesm2022/ngwr-pagination.mjs +65 -30
- package/fesm2022/ngwr-pagination.mjs.map +1 -1
- package/fesm2022/ngwr-popconfirm.mjs +3 -2
- package/fesm2022/ngwr-popconfirm.mjs.map +1 -1
- package/fesm2022/ngwr-popover.mjs +2 -1
- package/fesm2022/ngwr-popover.mjs.map +1 -1
- package/fesm2022/ngwr-qr.mjs +38 -1
- package/fesm2022/ngwr-qr.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +93 -34
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text.mjs +10 -3
- package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
- package/fesm2022/ngwr-splitter-testing.mjs.map +1 -1
- package/fesm2022/ngwr-squircle.mjs +2 -1
- package/fesm2022/ngwr-squircle.mjs.map +1 -1
- package/fesm2022/ngwr-statistic.mjs +35 -4
- package/fesm2022/ngwr-statistic.mjs.map +1 -1
- package/fesm2022/ngwr-table.mjs +35 -12
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-tabs-router.mjs +158 -0
- package/fesm2022/ngwr-tabs-router.mjs.map +1 -0
- package/fesm2022/ngwr-tabs-testing.mjs +13 -11
- package/fesm2022/ngwr-tabs-testing.mjs.map +1 -1
- package/fesm2022/ngwr-tabs.mjs +104 -12
- package/fesm2022/ngwr-tabs.mjs.map +1 -1
- package/fesm2022/ngwr-textarea.mjs +20 -1
- package/fesm2022/ngwr-textarea.mjs.map +1 -1
- package/fesm2022/ngwr-tour.mjs +1 -1
- package/fesm2022/ngwr-tour.mjs.map +1 -1
- package/fesm2022/ngwr-transfer.mjs +2 -2
- package/fesm2022/ngwr-transfer.mjs.map +1 -1
- package/fesm2022/ngwr-tree.mjs +18 -2
- package/fesm2022/ngwr-tree.mjs.map +1 -1
- package/fesm2022/ngwr-utils.mjs +143 -1
- package/fesm2022/ngwr-utils.mjs.map +1 -1
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/fesm2022/ngwr-window-testing.mjs +10 -2
- package/fesm2022/ngwr-window-testing.mjs.map +1 -1
- package/fesm2022/ngwr-window.mjs +4 -4
- package/fesm2022/ngwr-window.mjs.map +1 -1
- package/i18n/en.json +33 -6
- package/i18n/ru.json +33 -6
- package/llms-full.txt +20 -11
- package/llms.txt +47 -16
- package/mcp/catalog.js +3 -3
- package/mcp/providers.js +11 -0
- package/package.json +11 -3
- package/schematics/migrations/v13/index.js +101 -0
- package/schematics/migrations/v14/index.js +422 -0
- package/schematics/migrations.json +10 -0
- package/schematics/page/templates.js +1 -1
- package/schematics/use/symbol-map.json +1 -0
- package/skills/ngwr/SKILL.md +4 -1
- package/skills/ngwr/references/catalog.md +2 -0
- package/skills/ngwr/references/setup.md +4 -0
- package/stepper/styles/_index.scss +19 -1
- package/textarea/styles/_index.scss +14 -6
- package/toast/styles/_index.scss +12 -4
- package/types/ngwr-alert.d.ts +3 -3
- package/types/ngwr-blur-text.d.ts +8 -0
- package/types/ngwr-breadcrumbs.d.ts +1 -0
- package/types/ngwr-button.d.ts +2 -2
- package/types/ngwr-calendar-testing.d.ts +21 -4
- package/types/ngwr-calendar.d.ts +37 -0
- package/types/ngwr-circular-text.d.ts +9 -0
- package/types/ngwr-collapse.d.ts +19 -15
- package/types/ngwr-color-picker-testing.d.ts +10 -4
- package/types/ngwr-color-picker.d.ts +70 -2
- package/types/ngwr-command-palette.d.ts +14 -0
- package/types/ngwr-context-menu.d.ts +1 -1
- package/types/ngwr-date-adapters-fns.d.ts +71 -29
- package/types/ngwr-date-picker-testing.d.ts +206 -185
- package/types/ngwr-date-picker.d.ts +7 -0
- package/types/ngwr-date.d.ts +143 -13
- package/types/ngwr-drag-drop.d.ts +19 -0
- package/types/ngwr-dropdown.d.ts +1 -1
- package/types/ngwr-event-calendar-testing.d.ts +12 -9
- package/types/ngwr-file-upload.d.ts +31 -3
- package/types/ngwr-fuzzy-text.d.ts +5 -0
- package/types/ngwr-i18n.d.ts +89 -7
- package/types/ngwr-icon-adapters-feather.d.ts +11 -0
- package/types/ngwr-icon.d.ts +42 -1
- package/types/ngwr-input-number.d.ts +1 -1
- package/types/ngwr-input-otp.d.ts +3 -0
- package/types/ngwr-input.d.ts +11 -4
- package/types/ngwr-knob-testing.d.ts +8 -4
- package/types/ngwr-knob.d.ts +18 -0
- package/types/ngwr-loading-bar-router.d.ts +35 -0
- package/types/ngwr-loading-bar.d.ts +19 -9
- package/types/ngwr-mention.d.ts +13 -0
- package/types/ngwr-pagination-testing.d.ts +11 -10
- package/types/ngwr-pagination.d.ts +39 -10
- package/types/ngwr-popconfirm.d.ts +1 -1
- package/types/ngwr-popover.d.ts +1 -1
- package/types/ngwr-select.d.ts +65 -10
- package/types/ngwr-shiny-text.d.ts +1 -0
- package/types/ngwr-splitter-testing.d.ts +9 -4
- package/types/ngwr-squircle.d.ts +1 -1
- package/types/ngwr-statistic.d.ts +22 -2
- package/types/ngwr-table.d.ts +24 -5
- package/types/ngwr-tabs-router.d.ts +67 -0
- package/types/ngwr-tabs-testing.d.ts +12 -10
- package/types/ngwr-tabs.d.ts +103 -6
- package/types/ngwr-textarea.d.ts +9 -0
- package/types/ngwr-tree.d.ts +7 -0
- package/types/ngwr-utils.d.ts +79 -1
- package/types/ngwr-version.d.ts +1 -1
- package/types/ngwr-window-testing.d.ts +11 -3
- package/types/ngwr-window.d.ts +11 -4
- package/window/styles/_index.scss +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-tour.mjs","sources":["../../../projects/lib/tour/tour-popup.ts","../../../projects/lib/tour/tour-popup.html","../../../projects/lib/tour/tour.ts","../../../projects/lib/tour/ngwr-tour.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 { Component, InjectionToken, ViewEncapsulation, computed, inject } from '@angular/core';\n\nimport { WrButton } from 'ngwr/button';\nimport { readI18nText, useI18nFormatter } from 'ngwr/i18n';\n\nimport type { WrTourStep } from './interfaces';\n\n/** What the popup needs from the running tour, without importing the service (cycle). */\nexport interface WrTourController {\n readonly index: () => number;\n readonly total: () => number;\n readonly step: () => WrTourStep | null;\n /**\n * Whether this is the last step the user will actually see — the service looks\n * ahead for a reachable one, because a trailing step whose target is missing gets\n * skipped and the raw count would call the final card \"Next\".\n */\n readonly isLast: () => boolean;\n /**\n * Whether this is the first step the user will actually see — the service looks\n * BEHIND for a reachable one, for the mirror reason: a leading step whose target\n * is missing gets skipped, so the tour opens on a later index and the raw one\n * would put a Back button on the very first card.\n */\n readonly isFirst: () => boolean;\n next(): void;\n prev(): void;\n stop(): void;\n}\n\n/** Provided by `WrTour` when it attaches the popup portal. */\nexport const WR_TOUR_STEP = new InjectionToken<WrTourController>('WR_TOUR_STEP');\n\n/**\n * The card a tour shows next to the spotlit element. Rendered by `WrTour` into\n * a CDK overlay — not something you place yourself.\n *\n * @see https://ngwr.dev/reference/services/tour\n */\n@Component({\n selector: 'wr-tour-popup',\n templateUrl: './tour-popup.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-tour-popup', role: 'dialog', 'aria-modal': 'true', '[attr.aria-label]': 'ariaLabel()' },\n imports: [WrButton],\n})\nexport class WrTourPopup {\n protected readonly tour = inject(WR_TOUR_STEP);\n\n // `readI18nText`, not `useI18nText`: these have no consumer-facing input to\n // forward. Both helpers are reactive.\n protected readonly nextLabel = readI18nText('tour.next', 'Next');\n protected readonly backLabel = readI18nText('tour.back', 'Back');\n protected readonly doneLabel = readI18nText('tour.done', 'Done');\n protected readonly skipLabel = readI18nText('tour.skip', 'Skip tour');\n\n /** `Step {{current}} of {{total}}` — also the dialog's accessible name. */\n protected readonly progress = useI18nFormatter('tour.progress', 'Step {{current}} of {{total}}');\n\n // From the service, not `index() >= total() - 1`: a trailing step whose target is\n // missing gets skipped, so the raw count called the final card \"Next\" and then\n // ended the tour when it was pressed.\n protected readonly isLast = computed(() => this.tour.isLast());\n // Same story at the other end, and the same fix: `index() <= 0` rendered a Back\n // button on the first card of a tour whose leading step had been skipped, and\n // `prev()` walks off the start and returns — so the button did nothing at all.\n protected readonly isFirst = computed(() => this.tour.isFirst());\n\n protected readonly progressText = computed(() =>\n this.progress({ current: this.tour.index() + 1, total: this.tour.total() })\n );\n\n protected readonly ariaLabel = computed(() => {\n const title = this.tour.step()?.title;\n return title ? `${title} — ${this.progressText()}` : this.progressText();\n });\n}\n","@let step = tour.step();\n\n<p class=\"wr-tour-popup__progress\">{{ progressText() }}</p>\n\n@if (step?.title; as title) {\n <h2 class=\"wr-tour-popup__title\">{{ title }}</h2>\n}\n\n<p class=\"wr-tour-popup__content\">{{ step?.content }}</p>\n\n<div class=\"wr-tour-popup__actions\">\n <!-- The three buttons are told apart by a class rather than by their position in\n the row: the back button is conditional, so an index means one thing on the\n first step and another everywhere else — and the labels are translated. -->\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"medium\"\n outlined\n class=\"wr-tour-popup__action wr-tour-popup__action--skip\"\n (click)=\"tour.stop()\"\n >\n {{ skipLabel() }}\n </button>\n\n <span class=\"wr-tour-popup__spacer\"></span>\n\n @if (!isFirst()) {\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-tour-popup__action wr-tour-popup__action--back\"\n (click)=\"tour.prev()\"\n >\n {{ backLabel() }}\n </button>\n }\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n class=\"wr-tour-popup__action wr-tour-popup__action--next\"\n (click)=\"tour.next()\"\n >\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n</div>\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 ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';\nimport { Directionality } from '@angular/cdk/bidi';\nimport { type OverlayRef, ScrollStrategyOptions } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n DOCUMENT,\n DestroyRef,\n EnvironmentInjector,\n Injector,\n PLATFORM_ID,\n Service,\n computed,\n inject,\n signal,\n} from '@angular/core';\n\nimport { WR_OVERLAY, wrMirrorOffsets } from 'ngwr/overlay';\n\nimport type { WrTourPlacement, WrTourStep } from './interfaces';\nimport { WR_TOUR_STEP, WrTourPopup } from './tour-popup';\n\n/** Fallback order per preferred side — the opposite side first, then the sides. */\nconst FALLBACKS: Record<WrTourPlacement, readonly WrTourPlacement[]> = {\n bottom: ['bottom', 'top', 'right', 'left'],\n top: ['top', 'bottom', 'right', 'left'],\n left: ['left', 'right', 'bottom', 'top'],\n right: ['right', 'left', 'bottom', 'top'],\n};\n\nconst CONNECTED: Record<\n WrTourPlacement,\n {\n originX: 'start' | 'center' | 'end';\n originY: 'top' | 'center' | 'bottom';\n overlayX: 'start' | 'center' | 'end';\n overlayY: 'top' | 'center' | 'bottom';\n }\n> = {\n bottom: { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top' },\n top: { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom' },\n left: { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center' },\n right: { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center' },\n};\n\n/**\n * Guided product tour: a spotlight over one element at a time plus a popup\n * explaining it.\n *\n * Steps are data, not markup — call `start()` with an array and the service owns\n * the overlay, the cut-out, focus and the keyboard. A step whose target is not\n * on the page is skipped rather than shown floating, so a tour survives a\n * feature being hidden behind a flag or a permission.\n *\n * @example\n * ```ts\n * private readonly tour = inject(WrTour);\n *\n * showMeAround(): void {\n * this.tour.start([\n * { target: '[data-tour=\"search\"]', title: 'Search', content: 'Find anything by name.' },\n * { target: '[data-tour=\"filters\"]', content: 'Narrow the list down.', placement: 'right' },\n * ]);\n * }\n * ```\n *\n * @see https://ngwr.dev/reference/services/tour\n */\n@Service()\nexport class WrTour {\n private readonly overlay = inject(WR_OVERLAY);\n private readonly dir = inject(Directionality, { optional: true });\n private readonly scrollStrategies = inject(ScrollStrategyOptions);\n private readonly injector = inject(EnvironmentInjector);\n private readonly focusTrapFactory = inject(ConfigurableFocusTrapFactory);\n private readonly document = inject(DOCUMENT);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private readonly steps = signal<readonly WrTourStep[]>([]);\n private readonly cursor = signal(-1);\n\n private overlayRef: OverlayRef | null = null;\n private focusTrap: ConfigurableFocusTrap | null = null;\n private spotlight: HTMLElement | null = null;\n private detachKeydown: (() => void) | null = null;\n private detachReposition: (() => void) | null = null;\n private restoreFocus: HTMLElement | null = null;\n\n /** Whether a tour is running. */\n readonly active = computed(() => this.cursor() >= 0);\n\n /** Zero-based index of the current step, `-1` when idle. */\n readonly index = computed(() => this.cursor());\n\n /** How many steps this tour has. */\n readonly total = computed(() => this.steps().length);\n\n /** The step being shown, or `null` when idle. */\n readonly step = computed<WrTourStep | null>(() => this.steps()[this.cursor()] ?? null);\n\n // Whether any step AFTER the current one can actually be shown. Settled once per\n // move, in `goTo`, because reachability is a DOM question and not a signal.\n private readonly noStepAfter = signal(false);\n\n // The same question looking the other way. Settled in `goTo` for the same reason.\n private readonly noStepBefore = signal(true);\n\n /**\n * Whether this is the last step the user will see. Not `index() === total() - 1`:\n * a trailing step whose target is missing is skipped, so comparing against the\n * raw count left the primary button reading \"Next\" on the final card and then\n * ending the tour when it was pressed.\n */\n readonly isLast = computed(() => this.noStepAfter());\n\n /**\n * Whether this is the first step the user will see. Not `index() === 0`, for the\n * mirror of the reason above: a LEADING step whose target is missing is skipped,\n * so the tour opens on a later index and the raw one put a Back button on the\n * first card — one that did nothing at all, because `prev()` walks off the start\n * and returns without moving.\n */\n readonly isFirst = computed(() => this.noStepBefore());\n\n constructor() {\n // Everything this service owns lives OUTSIDE its own injector: a CDK overlay, a\n // div appended to `document.body`, a document keydown listener and two window\n // listeners. Nothing removes them when the injector goes, so a tour running at\n // teardown left its cut-out on the page.\n inject(DestroyRef).onDestroy(() => this.stop());\n }\n\n /**\n * Begin a tour. Restarts if one is already running. A no-op under SSR — a\n * tour is a client-side affordance and there is nothing to spotlight on the\n * server.\n */\n start(steps: readonly WrTourStep[]): void {\n if (!this.isBrowser || steps.length === 0) return;\n this.stop();\n this.steps.set(steps);\n this.restoreFocus = this.document.activeElement as HTMLElement | null;\n this.goTo(0, 1);\n }\n\n /** Advance; finishes the tour past the last step. */\n next(): void {\n this.goTo(this.cursor() + 1, 1);\n }\n\n /** Go back; stays put on the first step. */\n prev(): void {\n this.goTo(this.cursor() - 1, -1);\n }\n\n /** End the tour and return focus where it was. */\n stop(): void {\n this.teardownStep();\n this.steps.set([]);\n this.cursor.set(-1);\n this.restoreFocus?.focus?.();\n this.restoreFocus = null;\n }\n\n /**\n * Move to `index`, skipping steps whose target is not in the DOM. `direction`\n * says which way to keep skipping, so a missing step never traps the tour\n * between two dead ends.\n */\n private goTo(index: number, direction: 1 | -1): void {\n const steps = this.steps();\n let i = index;\n while (i >= 0 && i < steps.length && this.resolve(steps[i]) === null) i += direction;\n\n if (i < 0) {\n // Ran off the start while going back — stay where we were.\n return;\n }\n if (i >= steps.length) {\n this.stop();\n return;\n }\n\n this.teardownStep();\n this.cursor.set(i);\n this.noStepAfter.set(!this.anyResolvableAfter(i));\n this.noStepBefore.set(!this.anyResolvableBefore(i));\n this.openStep(steps[i]);\n }\n\n /** Whether the tour has a step past `index` that can actually be shown. */\n private anyResolvableAfter(index: number): boolean {\n const steps = this.steps();\n for (let i = index + 1; i < steps.length; i += 1) {\n if (this.resolve(steps[i]) !== null) return true;\n }\n return false;\n }\n\n /** Whether the tour has a step before `index` that can actually be shown. */\n private anyResolvableBefore(index: number): boolean {\n const steps = this.steps();\n for (let i = index - 1; i >= 0; i -= 1) {\n if (this.resolve(steps[i]) !== null) return true;\n }\n return false;\n }\n\n private resolve(step: WrTourStep): HTMLElement | null {\n if (typeof step.target !== 'string') return step.target.isConnected ? step.target : null;\n return this.document.querySelector<HTMLElement>(step.target);\n }\n\n private openStep(step: WrTourStep): void {\n const target = this.resolve(step);\n if (!target) return;\n\n target.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' });\n\n // The cut-out is a bare element sized to the target with an enormous spread\n // shadow: everything outside it dims, the target itself stays untouched.\n // Cheaper and far more robust than a clip-path polygon, and it needs no\n // recompute when the page reflows beyond moving the box.\n const spotlight = this.document.createElement('div');\n spotlight.className = 'wr-tour__spotlight';\n spotlight.setAttribute('aria-hidden', 'true');\n this.document.body.appendChild(spotlight);\n this.spotlight = spotlight;\n this.placeSpotlight(target);\n\n const placement = step.placement ?? 'bottom';\n const positionStrategy = this.overlay\n .position()\n .flexibleConnectedTo(target)\n .withPositions(\n wrMirrorOffsets(\n FALLBACKS[placement].map(side => ({\n ...CONNECTED[side],\n offsetY: side === 'bottom' ? 12 : side === 'top' ? -12 : 0,\n offsetX: side === 'right' ? 12 : side === 'left' ? -12 : 0,\n })),\n this.isRtl()\n )\n )\n .withPush(true);\n\n this.overlayRef = this.overlay.create({\n positionStrategy,\n scrollStrategy: this.scrollStrategies.reposition(),\n panelClass: 'wr-tour-overlay',\n });\n\n const portal = new ComponentPortal(\n WrTourPopup,\n null,\n Injector.create({ parent: this.injector, providers: [{ provide: WR_TOUR_STEP, useValue: this }] })\n );\n const ref = this.overlayRef.attach(portal);\n\n // Kept on the instance, not in a local: a trap registers with CDK's\n // `FocusTrapManager` and installs a capture-phase document focus listener, so an\n // undestroyed one leaks per STEP and every tour left a stack of them behind.\n this.focusTrap = this.focusTrapFactory.create(ref.location.nativeElement as HTMLElement);\n void this.focusTrap.focusInitialElementWhenReady();\n\n const onKey = (event: KeyboardEvent): void => {\n // The listener is on the document, so a step shown over a dialog would see the\n // Escape that dialog just handled and close the tour too.\n if (event.key === 'Escape' && !event.defaultPrevented) {\n event.preventDefault();\n this.stop();\n }\n };\n this.document.addEventListener('keydown', onKey);\n this.detachKeydown = () => this.document.removeEventListener('keydown', onKey);\n\n const reposition = (): void => this.placeSpotlight(target);\n window.addEventListener('scroll', reposition, true);\n window.addEventListener('resize', reposition);\n this.detachReposition = () => {\n window.removeEventListener('scroll', reposition, true);\n window.removeEventListener('resize', reposition);\n };\n }\n\n private placeSpotlight(target: HTMLElement): void {\n const el = this.spotlight;\n if (!el) return;\n const r = target.getBoundingClientRect();\n const pad = 6;\n el.style.top = `${r.top - pad}px`;\n el.style.left = `${r.left - pad}px`;\n el.style.width = `${r.width + pad * 2}px`;\n el.style.height = `${r.height + pad * 2}px`;\n }\n\n private teardownStep(): void {\n this.focusTrap?.destroy();\n this.focusTrap = null;\n this.detachKeydown?.();\n this.detachKeydown = null;\n this.detachReposition?.();\n this.detachReposition = null;\n this.overlayRef?.dispose();\n this.overlayRef = null;\n this.spotlight?.remove();\n this.spotlight = null;\n }\n\n /**\n * Whether the app reads right-to-left.\n *\n * Optional inject: `Directionality` is root-provided, so this is never null in\n * an app — but a bare `TestBed` that provides nothing should still get a\n * component that works, and defaulting to LTR is the honest fallback.\n */\n private isRtl(): boolean {\n return this.dir?.value === 'rtl';\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-tour.mjs","sources":["../../../projects/lib/tour/tour-popup.ts","../../../projects/lib/tour/tour-popup.html","../../../projects/lib/tour/tour.ts","../../../projects/lib/tour/ngwr-tour.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 { Component, InjectionToken, ViewEncapsulation, computed, inject } from '@angular/core';\n\nimport { WrButton } from 'ngwr/button';\nimport { readI18nText, useI18nFormatter } from 'ngwr/i18n';\n\nimport type { WrTourStep } from './interfaces';\n\n/** What the popup needs from the running tour, without importing the service (cycle). */\nexport interface WrTourController {\n readonly index: () => number;\n readonly total: () => number;\n readonly step: () => WrTourStep | null;\n /**\n * Whether this is the last step the user will actually see — the service looks\n * ahead for a reachable one, because a trailing step whose target is missing gets\n * skipped and the raw count would call the final card \"Next\".\n */\n readonly isLast: () => boolean;\n /**\n * Whether this is the first step the user will actually see — the service looks\n * BEHIND for a reachable one, for the mirror reason: a leading step whose target\n * is missing gets skipped, so the tour opens on a later index and the raw one\n * would put a Back button on the very first card.\n */\n readonly isFirst: () => boolean;\n next(): void;\n prev(): void;\n stop(): void;\n}\n\n/** Provided by `WrTour` when it attaches the popup portal. */\nexport const WR_TOUR_STEP = new InjectionToken<WrTourController>('WR_TOUR_STEP');\n\n/**\n * The card a tour shows next to the spotlit element. Rendered by `WrTour` into\n * a CDK overlay — not something you place yourself.\n *\n * @see https://ngwr.dev/reference/services/tour\n */\n@Component({\n selector: 'wr-tour-popup',\n templateUrl: './tour-popup.html',\n encapsulation: ViewEncapsulation.None,\n host: { class: 'wr-tour-popup', role: 'dialog', 'aria-modal': 'true', '[attr.aria-label]': 'ariaLabel()' },\n imports: [WrButton],\n})\nexport class WrTourPopup {\n protected readonly tour = inject(WR_TOUR_STEP);\n\n // `readI18nText`, not `useI18nText`: these have no consumer-facing input to\n // forward. Both helpers are reactive.\n protected readonly nextLabel = readI18nText('tour.next', 'Next');\n protected readonly backLabel = readI18nText('tour.back', 'Back');\n protected readonly doneLabel = readI18nText('tour.done', 'Done');\n protected readonly skipLabel = readI18nText('tour.skip', 'Skip tour');\n\n /** `Step {{current}} of {{total}}` — also the dialog's accessible name. */\n protected readonly progress = useI18nFormatter('tour.progress', 'Step {{current}} of {{total}}');\n\n // From the service, not `index() >= total() - 1`: a trailing step whose target is\n // missing gets skipped, so the raw count called the final card \"Next\" and then\n // ended the tour when it was pressed.\n protected readonly isLast = computed(() => this.tour.isLast());\n // Same story at the other end, and the same fix: `index() <= 0` rendered a Back\n // button on the first card of a tour whose leading step had been skipped, and\n // `prev()` walks off the start and returns — so the button did nothing at all.\n protected readonly isFirst = computed(() => this.tour.isFirst());\n\n protected readonly progressText = computed(() =>\n this.progress({ current: this.tour.index() + 1, total: this.tour.total() })\n );\n\n protected readonly ariaLabel = computed(() => {\n const title = this.tour.step()?.title;\n return title ? `${title} — ${this.progressText()}` : this.progressText();\n });\n}\n","@let step = tour.step();\n\n<p class=\"wr-tour-popup__progress\">{{ progressText() }}</p>\n\n@if (step?.title; as title) {\n <h2 class=\"wr-tour-popup__title\">{{ title }}</h2>\n}\n\n<p class=\"wr-tour-popup__content\">{{ step?.content }}</p>\n\n<div class=\"wr-tour-popup__actions\">\n <!-- The three buttons are told apart by a class rather than by their position in\n the row: the back button is conditional, so an index means one thing on the\n first step and another everywhere else — and the labels are translated. -->\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"medium\"\n outlined\n class=\"wr-tour-popup__action wr-tour-popup__action--skip\"\n (click)=\"tour.stop()\"\n >\n {{ skipLabel() }}\n </button>\n\n <span class=\"wr-tour-popup__spacer\"></span>\n\n @if (!isFirst()) {\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n outlined\n class=\"wr-tour-popup__action wr-tour-popup__action--back\"\n (click)=\"tour.prev()\"\n >\n {{ backLabel() }}\n </button>\n }\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n class=\"wr-tour-popup__action wr-tour-popup__action--next\"\n (click)=\"tour.next()\"\n >\n {{ isLast() ? doneLabel() : nextLabel() }}\n </button>\n</div>\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 ConfigurableFocusTrap, ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';\nimport { Directionality } from '@angular/cdk/bidi';\nimport { type OverlayRef, ScrollStrategyOptions } from '@angular/cdk/overlay';\nimport { ComponentPortal } from '@angular/cdk/portal';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n DOCUMENT,\n DestroyRef,\n EnvironmentInjector,\n Injector,\n PLATFORM_ID,\n Service,\n computed,\n inject,\n signal,\n} from '@angular/core';\n\nimport { WR_OVERLAY, wrMirrorOffsets } from 'ngwr/overlay';\n\nimport type { WrTourPlacement, WrTourStep } from './interfaces';\nimport { WR_TOUR_STEP, WrTourPopup } from './tour-popup';\n\n/** Fallback order per preferred side — the opposite side first, then the sides. */\nconst FALLBACKS: Record<WrTourPlacement, readonly WrTourPlacement[]> = {\n bottom: ['bottom', 'top', 'right', 'left'],\n top: ['top', 'bottom', 'right', 'left'],\n left: ['left', 'right', 'bottom', 'top'],\n right: ['right', 'left', 'bottom', 'top'],\n};\n\nconst CONNECTED: Record<\n WrTourPlacement,\n {\n originX: 'start' | 'center' | 'end';\n originY: 'top' | 'center' | 'bottom';\n overlayX: 'start' | 'center' | 'end';\n overlayY: 'top' | 'center' | 'bottom';\n }\n> = {\n bottom: { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top' },\n top: { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom' },\n left: { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center' },\n right: { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center' },\n};\n\n/**\n * Guided product tour: a spotlight over one element at a time plus a popup\n * explaining it.\n *\n * Steps are data, not markup — call `start()` with an array and the service owns\n * the overlay, the cut-out, focus and the keyboard. A step whose target is not\n * on the page is skipped rather than shown floating, so a tour survives a\n * feature being hidden behind a flag or a permission.\n *\n * @example\n * ```ts\n * private readonly tour = inject(WrTour);\n *\n * showMeAround(): void {\n * this.tour.start([\n * { target: '[data-tour=\"search\"]', title: 'Search', content: 'Find anything by name.' },\n * { target: '[data-tour=\"filters\"]', content: 'Narrow the list down.', placement: 'right' },\n * ]);\n * }\n * ```\n *\n * @see https://ngwr.dev/reference/services/tour\n */\n@Service()\nexport class WrTour {\n private readonly overlay = inject(WR_OVERLAY);\n private readonly dir = inject(Directionality, { optional: true });\n private readonly scrollStrategies = inject(ScrollStrategyOptions);\n private readonly injector = inject(EnvironmentInjector);\n private readonly focusTrapFactory = inject(ConfigurableFocusTrapFactory);\n private readonly document = inject(DOCUMENT);\n private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));\n\n private readonly steps = signal<readonly WrTourStep[]>([]);\n private readonly cursor = signal(-1);\n\n private overlayRef: OverlayRef | null = null;\n private focusTrap: ConfigurableFocusTrap | null = null;\n private spotlight: HTMLElement | null = null;\n private detachKeydown: (() => void) | null = null;\n private detachReposition: (() => void) | null = null;\n private restoreFocus: HTMLElement | null = null;\n\n /** Whether a tour is running. */\n readonly active = computed(() => this.cursor() >= 0);\n\n /** Zero-based index of the current step, `-1` when idle. */\n readonly index = computed(() => this.cursor());\n\n /** How many steps this tour has. */\n readonly total = computed(() => this.steps().length);\n\n /** The step being shown, or `null` when idle. */\n readonly step = computed<WrTourStep | null>(() => this.steps()[this.cursor()] ?? null);\n\n // Whether any step AFTER the current one can actually be shown. Settled once per\n // move, in `goTo`, because reachability is a DOM question and not a signal.\n private readonly noStepAfter = signal(false);\n\n // The same question looking the other way. Settled in `goTo` for the same reason.\n private readonly noStepBefore = signal(true);\n\n /**\n * Whether this is the last step the user will see. Not `index() === total() - 1`:\n * a trailing step whose target is missing is skipped, so comparing against the\n * raw count left the primary button reading \"Next\" on the final card and then\n * ending the tour when it was pressed.\n */\n readonly isLast = computed(() => this.noStepAfter());\n\n /**\n * Whether this is the first step the user will see. Not `index() === 0`, for the\n * mirror of the reason above: a LEADING step whose target is missing is skipped,\n * so the tour opens on a later index and the raw one put a Back button on the\n * first card — one that did nothing at all, because `prev()` walks off the start\n * and returns without moving.\n */\n readonly isFirst = computed(() => this.noStepBefore());\n\n constructor() {\n // Everything this service owns lives OUTSIDE its own injector: a CDK overlay, a\n // div appended to `document.body`, a document keydown listener and two window\n // listeners. Nothing removes them when the injector goes, so a tour running at\n // teardown left its cut-out on the page.\n inject(DestroyRef).onDestroy(() => this.stop());\n }\n\n /**\n * Begin a tour. Restarts if one is already running. A no-op under SSR — a\n * tour is a client-side affordance and there is nothing to spotlight on the\n * server.\n */\n start(steps: readonly WrTourStep[]): void {\n if (!this.isBrowser || steps.length === 0) return;\n this.stop();\n this.steps.set(steps);\n this.restoreFocus = this.document.activeElement as HTMLElement | null;\n this.goTo(0, 1);\n }\n\n /** Advance; finishes the tour past the last step. */\n next(): void {\n this.goTo(this.cursor() + 1, 1);\n }\n\n /** Go back; stays put on the first step. */\n prev(): void {\n this.goTo(this.cursor() - 1, -1);\n }\n\n /** End the tour and return focus where it was. */\n stop(): void {\n this.teardownStep();\n this.steps.set([]);\n this.cursor.set(-1);\n this.restoreFocus?.focus?.();\n this.restoreFocus = null;\n }\n\n /**\n * Move to `index`, skipping steps whose target is not in the DOM. `direction`\n * says which way to keep skipping, so a missing step never traps the tour\n * between two dead ends.\n */\n private goTo(index: number, direction: 1 | -1): void {\n const steps = this.steps();\n let i = index;\n while (i >= 0 && i < steps.length && this.resolve(steps[i]) === null) i += direction;\n\n if (i < 0) {\n // Ran off the start while going back — stay where we were.\n return;\n }\n if (i >= steps.length) {\n this.stop();\n return;\n }\n\n this.teardownStep();\n this.cursor.set(i);\n this.noStepAfter.set(!this.anyResolvableAfter(i));\n this.noStepBefore.set(!this.anyResolvableBefore(i));\n this.openStep(steps[i]);\n }\n\n /** Whether the tour has a step past `index` that can actually be shown. */\n private anyResolvableAfter(index: number): boolean {\n const steps = this.steps();\n for (let i = index + 1; i < steps.length; i += 1) {\n if (this.resolve(steps[i]) !== null) return true;\n }\n return false;\n }\n\n /** Whether the tour has a step before `index` that can actually be shown. */\n private anyResolvableBefore(index: number): boolean {\n const steps = this.steps();\n for (let i = index - 1; i >= 0; i -= 1) {\n if (this.resolve(steps[i]) !== null) return true;\n }\n return false;\n }\n\n private resolve(step: WrTourStep): HTMLElement | null {\n if (typeof step.target !== 'string') return step.target.isConnected ? step.target : null;\n return this.document.querySelector<HTMLElement>(step.target);\n }\n\n private openStep(step: WrTourStep): void {\n const target = this.resolve(step);\n if (!target) return;\n\n target.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' });\n\n // The cut-out is a bare element sized to the target with an enormous spread\n // shadow: everything outside it dims, the target itself stays untouched.\n // Cheaper and far more robust than a clip-path polygon, and it needs no\n // recompute when the page reflows beyond moving the box.\n const spotlight = this.document.createElement('div');\n spotlight.className = 'wr-tour__spotlight';\n spotlight.setAttribute('aria-hidden', 'true');\n this.document.body.appendChild(spotlight);\n this.spotlight = spotlight;\n this.placeSpotlight(target);\n\n const placement = step.placement ?? 'bottom';\n const positionStrategy = this.overlay\n .position()\n .flexibleConnectedTo(target)\n .withPositions(\n wrMirrorOffsets(\n FALLBACKS[placement].map(side => ({\n ...CONNECTED[side],\n offsetY: side === 'bottom' ? 12 : side === 'top' ? -12 : 0,\n offsetX: side === 'right' ? 12 : side === 'left' ? -12 : 0,\n })),\n this.isRtl()\n )\n )\n .withPush(true);\n\n this.overlayRef = this.overlay.create({\n positionStrategy,\n scrollStrategy: this.scrollStrategies.reposition(),\n panelClass: 'wr-tour-overlay',\n });\n\n const portal = new ComponentPortal(\n WrTourPopup,\n null,\n Injector.create({ parent: this.injector, providers: [{ provide: WR_TOUR_STEP, useValue: this }] })\n );\n const ref = this.overlayRef.attach(portal);\n\n // Kept on the instance, not in a local: a trap registers with CDK's\n // `FocusTrapManager` and installs a capture-phase document focus listener, so an\n // undestroyed one leaks per STEP and every tour left a stack of them behind.\n this.focusTrap = this.focusTrapFactory.create(ref.location.nativeElement as HTMLElement);\n void this.focusTrap.focusInitialElementWhenReady();\n\n const onKey = (event: KeyboardEvent): void => {\n // The listener is on the document, so a step shown over a dialog would see the\n // Escape that dialog just handled and close the tour too.\n if (event.key === 'Escape' && !event.defaultPrevented) {\n event.preventDefault();\n this.stop();\n }\n };\n this.document.addEventListener('keydown', onKey);\n this.detachKeydown = () => this.document.removeEventListener('keydown', onKey);\n\n const reposition = (): void => this.placeSpotlight(target);\n window.addEventListener('scroll', reposition, true);\n window.addEventListener('resize', reposition);\n this.detachReposition = () => {\n window.removeEventListener('scroll', reposition, true);\n window.removeEventListener('resize', reposition);\n };\n }\n\n private placeSpotlight(target: HTMLElement): void {\n const el = this.spotlight;\n if (!el) return;\n const r = target.getBoundingClientRect();\n const pad = 6;\n el.style.top = `${r.top - pad}px`;\n el.style.left = `${r.left - pad}px`;\n el.style.width = `${r.width + pad * 2}px`;\n el.style.height = `${r.height + pad * 2}px`;\n }\n\n private teardownStep(): void {\n this.focusTrap?.destroy();\n this.focusTrap = null;\n this.detachKeydown?.();\n this.detachKeydown = null;\n this.detachReposition?.();\n this.detachReposition = null;\n this.overlayRef?.dispose();\n this.overlayRef = null;\n this.spotlight?.remove();\n this.spotlight = null;\n }\n\n /**\n * Whether the app reads right-to-left.\n *\n * Optional inject: `Directionality` is root-provided, so this is never null in\n * an app — but a bare `TestBed` that provides nothing should still get a\n * component that works, and defaulting to LTR is the honest fallback.\n */\n private isRtl(): boolean {\n return this.dir?.value === 'rtl';\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -224,7 +224,7 @@ class WrTransfer {
|
|
|
224
224
|
};
|
|
225
225
|
}
|
|
226
226
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTransfer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
227
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrTransfer, isStandalone: true, selector: "wr-transfer", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", 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 }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, sourceTitle: { classPropertyName: "sourceTitle", publicName: "sourceTitle", isSignal: true, isRequired: false, transformFunction: null }, targetTitle: { classPropertyName: "targetTitle", publicName: "targetTitle", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, selectAllLabel: { classPropertyName: "selectAllLabel", publicName: "selectAllLabel", isSignal: true, isRequired: false, transformFunction: null }, toTargetLabel: { classPropertyName: "toTargetLabel", publicName: "toTargetLabel", isSignal: true, isRequired: false, transformFunction: null }, toSourceLabel: { classPropertyName: "toSourceLabel", publicName: "toSourceLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { attributes: { "role": "group" }, properties: { "class": "classes()", "attr.aria-invalid": "fieldAria.ariaInvalid()", "attr.aria-describedby": "fieldAria.describedBy()" } }, providers: [{ provide: WR_FORM_FIELD, useValue: null }], ngImport: i0, template: "<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid \u2014 an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"`${title} \u2014 ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n
|
|
227
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrTransfer, isStandalone: true, selector: "wr-transfer", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", 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 }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, sourceTitle: { classPropertyName: "sourceTitle", publicName: "sourceTitle", isSignal: true, isRequired: false, transformFunction: null }, targetTitle: { classPropertyName: "targetTitle", publicName: "targetTitle", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, selectAllLabel: { classPropertyName: "selectAllLabel", publicName: "selectAllLabel", isSignal: true, isRequired: false, transformFunction: null }, toTargetLabel: { classPropertyName: "toTargetLabel", publicName: "toTargetLabel", isSignal: true, isRequired: false, transformFunction: null }, toSourceLabel: { classPropertyName: "toSourceLabel", publicName: "toSourceLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touch: "touch" }, host: { attributes: { "role": "group" }, properties: { "class": "classes()", "attr.aria-invalid": "fieldAria.ariaInvalid()", "attr.aria-describedby": "fieldAria.describedBy()" } }, providers: [{ provide: WR_FORM_FIELD, useValue: null }], ngImport: i0, template: "<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid \u2014 an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"`${title} \u2014 ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n size=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} \u2014 ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\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 <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\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 </div>\n }\n}\n", dependencies: [{ 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: WrCheckbox, selector: "wr-checkbox", inputs: ["id", "checkboxValue", "ariaLabel", "checked", "disabled", "readonly", "size", "icon", "indeterminate"], outputs: ["checkedChange", "touch"] }, { kind: "directive", type: WrInput, selector: "input[wrInput], textarea[wrInput]", inputs: ["size", "rounded"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
228
228
|
}
|
|
229
229
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTransfer, decorators: [{
|
|
230
230
|
type: Component,
|
|
@@ -236,7 +236,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
|
|
|
236
236
|
role: 'group',
|
|
237
237
|
'[attr.aria-invalid]': 'fieldAria.ariaInvalid()',
|
|
238
238
|
'[attr.aria-describedby]': 'fieldAria.describedBy()',
|
|
239
|
-
}, providers: [{ provide: WR_FORM_FIELD, useValue: null }], imports: [WrButton, WrCheckbox, WrInput], template: "<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid \u2014 an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"`${title} \u2014 ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n
|
|
239
|
+
}, providers: [{ provide: WR_FORM_FIELD, useValue: null }], imports: [WrButton, WrCheckbox, WrInput], template: "<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid \u2014 an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"`${title} \u2014 ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n size=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} \u2014 ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\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 <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\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 </div>\n }\n}\n" }]
|
|
240
240
|
}], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], sourceTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceTitle", required: false }] }], targetTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "targetTitle", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], selectAllLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllLabel", required: false }] }], toTargetLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "toTargetLabel", required: false }] }], toSourceLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "toSourceLabel", required: false }] }] } });
|
|
241
241
|
|
|
242
242
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-transfer.mjs","sources":["../../../projects/lib/transfer/transfer.ts","../../../projects/lib/transfer/transfer.html","../../../projects/lib/transfer/ngwr-transfer.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, computed, input, model, output, signal } from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { WrButton } from 'ngwr/button';\nimport { WrCheckbox } from 'ngwr/checkbox';\nimport { WR_FORM_FIELD, useFormFieldAria } from 'ngwr/form';\nimport { useI18nFormatter, useI18nText } from 'ngwr/i18n';\nimport { WrInput } from 'ngwr/input';\n\nimport type { WrTransferItem } from './interfaces';\n\n/** A pane's rows plus the header state derived from them. */\ninterface PaneState {\n readonly rows: readonly WrTransferItem[];\n /**\n * The staged values this pane is actually SHOWING — visible and enabled. The\n * staging signals can hold more than that (a filter change hides rows without\n * unstaging them), and every consumer of the staged set has to agree on which\n * one it means: the count, the header checkbox and the move all read this.\n */\n readonly checked: readonly unknown[];\n readonly checkedCount: number;\n readonly enabledCount: number;\n readonly allChecked: boolean;\n readonly someChecked: boolean;\n}\n\n/**\n * Dual-listbox picker: two panes and the buttons that move rows between them.\n *\n * The full item set is `[items]` and the RIGHT pane is the value — `[(value)]`\n * holds the chosen `WrTransferItem['value']`s, everything else stays left. The\n * checked state inside each pane is transient staging, deliberately not part of\n * the value: what a form cares about is which rows ended up on the right.\n *\n * A signal-forms native control (`FormValueControl<readonly unknown[]>`), so\n * `[formField]` binds straight to `value`; `[(ngModel)]` and reactive forms keep\n * working through Angular's bridge, and `[(value)]` is the standalone shape.\n *\n * @example\n * ```html\n * <!-- Standalone -->\n * <wr-transfer [items]=\"permissions\" [(value)]=\"granted\" />\n *\n * <!-- Searchable, with pane titles -->\n * <wr-transfer\n * searchable\n * sourceTitle=\"Available\"\n * targetTitle=\"Granted\"\n * [items]=\"permissions\"\n * [formField]=\"form.granted\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/transfer\n */\n@Component({\n selector: 'wr-transfer',\n templateUrl: './transfer.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n // `role=\"group\"` so the error state below has somewhere legible to live: the\n // control is a composite with no single element carrying its semantics, and\n // `aria-invalid` on a bare `generic` host is announced by nothing.\n role: 'group',\n '[attr.aria-invalid]': 'fieldAria.ariaInvalid()',\n '[attr.aria-describedby]': 'fieldAria.describedBy()',\n },\n // The transfer is the control a `<wr-form-field>` wraps; the checkboxes and\n // the search inputs in its own template are parts, not controls. Without this\n // shield each of them found the field's token and announced the one error as\n // its own — which is why `fieldAria` below reads the field with `skipSelf`.\n providers: [{ provide: WR_FORM_FIELD, useValue: null }],\n imports: [WrButton, WrCheckbox, WrInput],\n})\nexport class WrTransfer implements FormValueControl<readonly unknown[]> {\n /** Every row, in either pane. Membership of the right pane is `value`. */\n readonly items = input<readonly WrTransferItem[]>([]);\n\n /**\n * Values currently in the RIGHT pane. Two-way bindable; bound automatically\n * by `[formField]` / `[(ngModel)]`.\n */\n readonly value = model<readonly unknown[]>([]);\n\n /** Emitted on blur / commit so a bound field marks itself touched. */\n readonly touch = output<void>();\n\n /** Disable the whole control. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Refuse changes to the value while both panes stay focusable and readable.\n * Bound automatically from the field's readonly state when used with\n * `[formField]`.\n *\n * The move buttons go inert and the row checkboxes go read-only; SEARCH keeps\n * working, because filtering a pane changes what is shown and not what is\n * chosen. No `aria-readonly` on the host: role `group` does not support the\n * state, so each row's checkbox mirrors its own instead.\n *\n * @default false\n */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** The surrounding `<wr-form-field>`'s error state. @internal */\n protected readonly fieldAria = useFormFieldAria({ skipSelf: true });\n\n /** Show a filter box above each pane. @default false */\n readonly searchable = input(false, { transform: coerceBooleanProperty });\n\n /** Heading above the left pane. Falls back to `transfer.source`. */\n readonly sourceTitle = input<string | null>(null);\n\n /** Heading above the right pane. Falls back to `transfer.target`. */\n readonly targetTitle = input<string | null>(null);\n\n /** Placeholder in both filter boxes. Falls back to `transfer.search`. */\n readonly searchPlaceholder = input<string | null>(null);\n\n /** Shown in a pane with no rows. Falls back to `transfer.empty`. */\n readonly emptyText = input<string | null>(null);\n\n /**\n * Accessible name of each pane's select-all checkbox, composed with the pane\n * heading. Falls back to `transfer.selectAll`.\n */\n readonly selectAllLabel = input<string | null>(null);\n\n /** Accessible name of the move-right button. Falls back to `transfer.toTarget`. */\n readonly toTargetLabel = input<string | null>(null);\n\n /** Accessible name of the move-left button. Falls back to `transfer.toSource`. */\n readonly toSourceLabel = input<string | null>(null);\n\n protected readonly resolvedSourceTitle = useI18nText(this.sourceTitle, 'transfer.source', 'Available');\n protected readonly resolvedTargetTitle = useI18nText(this.targetTitle, 'transfer.target', 'Selected');\n protected readonly resolvedSearch = useI18nText(this.searchPlaceholder, 'transfer.search', 'Search');\n protected readonly resolvedEmpty = useI18nText(this.emptyText, 'transfer.empty', 'Nothing here');\n protected readonly resolvedSelectAll = useI18nText(this.selectAllLabel, 'transfer.selectAll', 'Select all');\n protected readonly resolvedToTarget = useI18nText(this.toTargetLabel, 'transfer.toTarget', 'Move to selected');\n protected readonly resolvedToSource = useI18nText(this.toSourceLabel, 'transfer.toSource', 'Move to available');\n\n /** `{{checked}} / {{total}}` under each heading. @internal */\n protected readonly countLabel = useI18nFormatter('transfer.count', '{{checked}} / {{total}}');\n\n /** Pane order — the template iterates it so both panes share one block. */\n protected readonly panes = ['source', 'target'] as const;\n\n private readonly sourceQuery = signal('');\n private readonly targetQuery = signal('');\n\n /** Staged rows, per pane. Cleared as soon as a move commits them. */\n private readonly sourceChecked = signal<readonly unknown[]>([]);\n private readonly targetChecked = signal<readonly unknown[]>([]);\n\n // The value as a guaranteed array — a classic-forms binding can write null even\n // though the type is `readonly unknown[]`, so normalise every read. Same hazard\n // `wr-checkbox-group` guards; here an unguarded `.map` threw outright.\n private readonly selected = computed<readonly unknown[]>(() => {\n const v = this.value();\n return Array.isArray(v) ? v : [];\n });\n\n private readonly valueSet = computed(() => new Set(this.selected()));\n\n protected readonly source = computed<PaneState>(() =>\n this.pane(\n this.items().filter(item => !this.valueSet().has(item.value)),\n this.sourceQuery(),\n this.sourceChecked()\n )\n );\n\n protected readonly target = computed<PaneState>(() =>\n this.pane(\n // Right-pane order follows `value`, not `items`: the order a user built is\n // the one they expect to read back.\n this.selected()\n .map(v => this.items().find(item => item.value === v))\n .filter((item): item is WrTransferItem => item !== undefined),\n this.targetQuery(),\n this.targetChecked()\n )\n );\n\n protected readonly canMoveRight = computed(\n () => !this.disabled() && !this.readonly() && this.source().checkedCount > 0\n );\n protected readonly canMoveLeft = computed(\n () => !this.disabled() && !this.readonly() && this.target().checkedCount > 0\n );\n\n protected readonly classes = computed(() => {\n const parts = ['wr-transfer'];\n if (this.disabled()) parts.push('wr-transfer--disabled');\n else if (this.readonly()) parts.push('wr-transfer--readonly');\n if (this.searchable()) parts.push('wr-transfer--searchable');\n return parts.join(' ');\n });\n\n protected isChecked(pane: 'source' | 'target', item: WrTransferItem): boolean {\n return (pane === 'source' ? this.sourceChecked() : this.targetChecked()).includes(item.value);\n }\n\n protected toggle(pane: 'source' | 'target', item: WrTransferItem, checked: boolean): void {\n if (item.disabled || this.readonly()) return;\n const box = pane === 'source' ? this.sourceChecked : this.targetChecked;\n const current = box();\n if (checked) {\n box.set([...current, item.value]);\n return;\n }\n // A `Set` compares with SameValueZero, which is what `WrTransferItem['value']`\n // is documented to use and what `isChecked`'s `includes` already does. A `!==`\n // filter cannot remove `NaN` from its own list, so such a row drew itself\n // unchecked while staying staged.\n const next = new Set(current);\n next.delete(item.value);\n box.set([...next]);\n }\n\n /** Header checkbox — stages or clears every enabled row the filter shows. */\n protected toggleAll(pane: 'source' | 'target', checked: boolean): void {\n if (this.readonly()) return;\n const state = pane === 'source' ? this.source() : this.target();\n const box = pane === 'source' ? this.sourceChecked : this.targetChecked;\n box.set(checked ? state.rows.filter(item => !item.disabled).map(item => item.value) : []);\n }\n\n protected onSearch(pane: 'source' | 'target', event: Event): void {\n const query = (event.target as HTMLInputElement | null)?.value ?? '';\n (pane === 'source' ? this.sourceQuery : this.targetQuery).set(query);\n }\n\n protected moveRight(): void {\n if (this.disabled() || this.readonly()) return;\n // `source().checked`, not the raw staging box: rows a filter change hid are\n // neither counted nor ticked any more, so moving them would transfer rows the\n // pane never showed as chosen — and, since staging outlives an external write\n // to `value`, could land the same value on the right twice.\n const moving = this.source().checked;\n if (moving.length === 0) return;\n this.value.set([...this.selected(), ...moving]);\n this.sourceChecked.set([]);\n this.touch.emit();\n }\n\n protected moveLeft(): void {\n if (this.disabled() || this.readonly()) return;\n const moving = new Set(this.target().checked);\n if (moving.size === 0) return;\n this.value.set(this.selected().filter(v => !moving.has(v)));\n this.targetChecked.set([]);\n this.touch.emit();\n }\n\n /** Filter, then derive the header state from what survived. */\n private pane(rows: readonly WrTransferItem[], query: string, checked: readonly unknown[]): PaneState {\n const q = query.trim().toLowerCase();\n const visible = q ? rows.filter(item => item.label.toLowerCase().includes(q)) : rows;\n const enabled = visible.filter(item => !item.disabled);\n const staged = new Set(checked);\n const shown = enabled.filter(item => staged.has(item.value)).map(item => item.value);\n return {\n rows: visible,\n checked: shown,\n checkedCount: shown.length,\n enabledCount: enabled.length,\n allChecked: enabled.length > 0 && shown.length === enabled.length,\n someChecked: shown.length > 0 && shown.length < enabled.length,\n };\n }\n}\n","<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid — an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"`${title} — ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n wrSize=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} — ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\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 <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\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 </div>\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-transfer.mjs","sources":["../../../projects/lib/transfer/transfer.ts","../../../projects/lib/transfer/transfer.html","../../../projects/lib/transfer/ngwr-transfer.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { coerceBooleanProperty } from '@angular/cdk/coercion';\nimport { Component, ViewEncapsulation, computed, input, model, output, signal } from '@angular/core';\nimport type { FormValueControl } from '@angular/forms/signals';\n\nimport { WrButton } from 'ngwr/button';\nimport { WrCheckbox } from 'ngwr/checkbox';\nimport { WR_FORM_FIELD, useFormFieldAria } from 'ngwr/form';\nimport { useI18nFormatter, useI18nText } from 'ngwr/i18n';\nimport { WrInput } from 'ngwr/input';\n\nimport type { WrTransferItem } from './interfaces';\n\n/** A pane's rows plus the header state derived from them. */\ninterface PaneState {\n readonly rows: readonly WrTransferItem[];\n /**\n * The staged values this pane is actually SHOWING — visible and enabled. The\n * staging signals can hold more than that (a filter change hides rows without\n * unstaging them), and every consumer of the staged set has to agree on which\n * one it means: the count, the header checkbox and the move all read this.\n */\n readonly checked: readonly unknown[];\n readonly checkedCount: number;\n readonly enabledCount: number;\n readonly allChecked: boolean;\n readonly someChecked: boolean;\n}\n\n/**\n * Dual-listbox picker: two panes and the buttons that move rows between them.\n *\n * The full item set is `[items]` and the RIGHT pane is the value — `[(value)]`\n * holds the chosen `WrTransferItem['value']`s, everything else stays left. The\n * checked state inside each pane is transient staging, deliberately not part of\n * the value: what a form cares about is which rows ended up on the right.\n *\n * A signal-forms native control (`FormValueControl<readonly unknown[]>`), so\n * `[formField]` binds straight to `value`; `[(ngModel)]` and reactive forms keep\n * working through Angular's bridge, and `[(value)]` is the standalone shape.\n *\n * @example\n * ```html\n * <!-- Standalone -->\n * <wr-transfer [items]=\"permissions\" [(value)]=\"granted\" />\n *\n * <!-- Searchable, with pane titles -->\n * <wr-transfer\n * searchable\n * sourceTitle=\"Available\"\n * targetTitle=\"Granted\"\n * [items]=\"permissions\"\n * [formField]=\"form.granted\"\n * />\n * ```\n *\n * @see https://ngwr.dev/reference/components/transfer\n */\n@Component({\n selector: 'wr-transfer',\n templateUrl: './transfer.html',\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n // `role=\"group\"` so the error state below has somewhere legible to live: the\n // control is a composite with no single element carrying its semantics, and\n // `aria-invalid` on a bare `generic` host is announced by nothing.\n role: 'group',\n '[attr.aria-invalid]': 'fieldAria.ariaInvalid()',\n '[attr.aria-describedby]': 'fieldAria.describedBy()',\n },\n // The transfer is the control a `<wr-form-field>` wraps; the checkboxes and\n // the search inputs in its own template are parts, not controls. Without this\n // shield each of them found the field's token and announced the one error as\n // its own — which is why `fieldAria` below reads the field with `skipSelf`.\n providers: [{ provide: WR_FORM_FIELD, useValue: null }],\n imports: [WrButton, WrCheckbox, WrInput],\n})\nexport class WrTransfer implements FormValueControl<readonly unknown[]> {\n /** Every row, in either pane. Membership of the right pane is `value`. */\n readonly items = input<readonly WrTransferItem[]>([]);\n\n /**\n * Values currently in the RIGHT pane. Two-way bindable; bound automatically\n * by `[formField]` / `[(ngModel)]`.\n */\n readonly value = model<readonly unknown[]>([]);\n\n /** Emitted on blur / commit so a bound field marks itself touched. */\n readonly touch = output<void>();\n\n /** Disable the whole control. @default false */\n readonly disabled = input(false, { transform: coerceBooleanProperty });\n\n /**\n * Refuse changes to the value while both panes stay focusable and readable.\n * Bound automatically from the field's readonly state when used with\n * `[formField]`.\n *\n * The move buttons go inert and the row checkboxes go read-only; SEARCH keeps\n * working, because filtering a pane changes what is shown and not what is\n * chosen. No `aria-readonly` on the host: role `group` does not support the\n * state, so each row's checkbox mirrors its own instead.\n *\n * @default false\n */\n readonly readonly = input(false, { transform: coerceBooleanProperty });\n\n /** The surrounding `<wr-form-field>`'s error state. @internal */\n protected readonly fieldAria = useFormFieldAria({ skipSelf: true });\n\n /** Show a filter box above each pane. @default false */\n readonly searchable = input(false, { transform: coerceBooleanProperty });\n\n /** Heading above the left pane. Falls back to `transfer.source`. */\n readonly sourceTitle = input<string | null>(null);\n\n /** Heading above the right pane. Falls back to `transfer.target`. */\n readonly targetTitle = input<string | null>(null);\n\n /** Placeholder in both filter boxes. Falls back to `transfer.search`. */\n readonly searchPlaceholder = input<string | null>(null);\n\n /** Shown in a pane with no rows. Falls back to `transfer.empty`. */\n readonly emptyText = input<string | null>(null);\n\n /**\n * Accessible name of each pane's select-all checkbox, composed with the pane\n * heading. Falls back to `transfer.selectAll`.\n */\n readonly selectAllLabel = input<string | null>(null);\n\n /** Accessible name of the move-right button. Falls back to `transfer.toTarget`. */\n readonly toTargetLabel = input<string | null>(null);\n\n /** Accessible name of the move-left button. Falls back to `transfer.toSource`. */\n readonly toSourceLabel = input<string | null>(null);\n\n protected readonly resolvedSourceTitle = useI18nText(this.sourceTitle, 'transfer.source', 'Available');\n protected readonly resolvedTargetTitle = useI18nText(this.targetTitle, 'transfer.target', 'Selected');\n protected readonly resolvedSearch = useI18nText(this.searchPlaceholder, 'transfer.search', 'Search');\n protected readonly resolvedEmpty = useI18nText(this.emptyText, 'transfer.empty', 'Nothing here');\n protected readonly resolvedSelectAll = useI18nText(this.selectAllLabel, 'transfer.selectAll', 'Select all');\n protected readonly resolvedToTarget = useI18nText(this.toTargetLabel, 'transfer.toTarget', 'Move to selected');\n protected readonly resolvedToSource = useI18nText(this.toSourceLabel, 'transfer.toSource', 'Move to available');\n\n /** `{{checked}} / {{total}}` under each heading. @internal */\n protected readonly countLabel = useI18nFormatter('transfer.count', '{{checked}} / {{total}}');\n\n /** Pane order — the template iterates it so both panes share one block. */\n protected readonly panes = ['source', 'target'] as const;\n\n private readonly sourceQuery = signal('');\n private readonly targetQuery = signal('');\n\n /** Staged rows, per pane. Cleared as soon as a move commits them. */\n private readonly sourceChecked = signal<readonly unknown[]>([]);\n private readonly targetChecked = signal<readonly unknown[]>([]);\n\n // The value as a guaranteed array — a classic-forms binding can write null even\n // though the type is `readonly unknown[]`, so normalise every read. Same hazard\n // `wr-checkbox-group` guards; here an unguarded `.map` threw outright.\n private readonly selected = computed<readonly unknown[]>(() => {\n const v = this.value();\n return Array.isArray(v) ? v : [];\n });\n\n private readonly valueSet = computed(() => new Set(this.selected()));\n\n protected readonly source = computed<PaneState>(() =>\n this.pane(\n this.items().filter(item => !this.valueSet().has(item.value)),\n this.sourceQuery(),\n this.sourceChecked()\n )\n );\n\n protected readonly target = computed<PaneState>(() =>\n this.pane(\n // Right-pane order follows `value`, not `items`: the order a user built is\n // the one they expect to read back.\n this.selected()\n .map(v => this.items().find(item => item.value === v))\n .filter((item): item is WrTransferItem => item !== undefined),\n this.targetQuery(),\n this.targetChecked()\n )\n );\n\n protected readonly canMoveRight = computed(\n () => !this.disabled() && !this.readonly() && this.source().checkedCount > 0\n );\n protected readonly canMoveLeft = computed(\n () => !this.disabled() && !this.readonly() && this.target().checkedCount > 0\n );\n\n protected readonly classes = computed(() => {\n const parts = ['wr-transfer'];\n if (this.disabled()) parts.push('wr-transfer--disabled');\n else if (this.readonly()) parts.push('wr-transfer--readonly');\n if (this.searchable()) parts.push('wr-transfer--searchable');\n return parts.join(' ');\n });\n\n protected isChecked(pane: 'source' | 'target', item: WrTransferItem): boolean {\n return (pane === 'source' ? this.sourceChecked() : this.targetChecked()).includes(item.value);\n }\n\n protected toggle(pane: 'source' | 'target', item: WrTransferItem, checked: boolean): void {\n if (item.disabled || this.readonly()) return;\n const box = pane === 'source' ? this.sourceChecked : this.targetChecked;\n const current = box();\n if (checked) {\n box.set([...current, item.value]);\n return;\n }\n // A `Set` compares with SameValueZero, which is what `WrTransferItem['value']`\n // is documented to use and what `isChecked`'s `includes` already does. A `!==`\n // filter cannot remove `NaN` from its own list, so such a row drew itself\n // unchecked while staying staged.\n const next = new Set(current);\n next.delete(item.value);\n box.set([...next]);\n }\n\n /** Header checkbox — stages or clears every enabled row the filter shows. */\n protected toggleAll(pane: 'source' | 'target', checked: boolean): void {\n if (this.readonly()) return;\n const state = pane === 'source' ? this.source() : this.target();\n const box = pane === 'source' ? this.sourceChecked : this.targetChecked;\n box.set(checked ? state.rows.filter(item => !item.disabled).map(item => item.value) : []);\n }\n\n protected onSearch(pane: 'source' | 'target', event: Event): void {\n const query = (event.target as HTMLInputElement | null)?.value ?? '';\n (pane === 'source' ? this.sourceQuery : this.targetQuery).set(query);\n }\n\n protected moveRight(): void {\n if (this.disabled() || this.readonly()) return;\n // `source().checked`, not the raw staging box: rows a filter change hid are\n // neither counted nor ticked any more, so moving them would transfer rows the\n // pane never showed as chosen — and, since staging outlives an external write\n // to `value`, could land the same value on the right twice.\n const moving = this.source().checked;\n if (moving.length === 0) return;\n this.value.set([...this.selected(), ...moving]);\n this.sourceChecked.set([]);\n this.touch.emit();\n }\n\n protected moveLeft(): void {\n if (this.disabled() || this.readonly()) return;\n const moving = new Set(this.target().checked);\n if (moving.size === 0) return;\n this.value.set(this.selected().filter(v => !moving.has(v)));\n this.targetChecked.set([]);\n this.touch.emit();\n }\n\n /** Filter, then derive the header state from what survived. */\n private pane(rows: readonly WrTransferItem[], query: string, checked: readonly unknown[]): PaneState {\n const q = query.trim().toLowerCase();\n const visible = q ? rows.filter(item => item.label.toLowerCase().includes(q)) : rows;\n const enabled = visible.filter(item => !item.disabled);\n const staged = new Set(checked);\n const shown = enabled.filter(item => staged.has(item.value)).map(item => item.value);\n return {\n rows: visible,\n checked: shown,\n checkedCount: shown.length,\n enabledCount: enabled.length,\n allChecked: enabled.length > 0 && shown.length === enabled.length,\n someChecked: shown.length > 0 && shown.length < enabled.length,\n };\n }\n}\n","<!--\n Two panes and the buttons between them. Each pane is a PLAIN `<ul>` of\n checkboxes, deliberately: dressing it as a `role=\"listbox\"` of `role=\"option\"`\n rows reads better in the abstract but is invalid — an option may not contain an\n interactive control, and the axe gate says so. The checkbox is the thing the\n user operates, so it is the thing the semantics should describe; the pane\n heading names the group.\n-->\n@for (pane of panes; track pane) {\n @let state = pane === 'source' ? source() : target();\n @let title = pane === 'source' ? resolvedSourceTitle() : resolvedTargetTitle();\n\n <div class=\"wr-transfer__pane\" [class.wr-transfer__pane--target]=\"pane === 'target'\">\n <div class=\"wr-transfer__head\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"state.allChecked\"\n [indeterminate]=\"state.someChecked\"\n [disabled]=\"disabled() || state.enabledCount === 0\"\n [readonly]=\"readonly()\"\n [ariaLabel]=\"`${title} — ${resolvedSelectAll()}`\"\n (checkedChange)=\"toggleAll(pane, $event)\"\n />\n <span class=\"wr-transfer__title\">{{ title }}</span>\n <span class=\"wr-transfer__count\">{{\n countLabel({ checked: state.checkedCount, total: state.rows.length })\n }}</span>\n </div>\n\n @if (searchable()) {\n <div class=\"wr-transfer__search\">\n <input\n wrInput\n size=\"sm\"\n type=\"search\"\n [placeholder]=\"resolvedSearch()\"\n [disabled]=\"disabled()\"\n [attr.aria-label]=\"`${title} — ${resolvedSearch()}`\"\n (input)=\"onSearch(pane, $event)\"\n />\n </div>\n }\n\n <ul class=\"wr-transfer__list\" [attr.aria-label]=\"title\">\n @for (item of state.rows; track item.value) {\n <li class=\"wr-transfer__item\" [class.wr-transfer__item--disabled]=\"item.disabled\">\n <wr-checkbox\n size=\"sm\"\n [checked]=\"isChecked(pane, item)\"\n [disabled]=\"disabled() || !!item.disabled\"\n [readonly]=\"readonly()\"\n (checkedChange)=\"toggle(pane, item, $event)\"\n (touch)=\"touch.emit()\"\n >\n {{ item.label }}\n </wr-checkbox>\n </li>\n } @empty {\n <li class=\"wr-transfer__empty\">{{ resolvedEmpty() }}</li>\n }\n </ul>\n </div>\n\n <!-- The buttons live between the panes, so they are emitted after the first. -->\n @if (pane === 'source') {\n <div class=\"wr-transfer__actions\">\n <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-target\"\n [disabled]=\"!canMoveRight()\"\n [attr.aria-label]=\"resolvedToTarget()\"\n (click)=\"moveRight()\"\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 <button\n wr-btn\n type=\"button\"\n size=\"sm\"\n color=\"primary\"\n outlined\n class=\"wr-transfer__move wr-transfer__move--to-source\"\n [disabled]=\"!canMoveLeft()\"\n [attr.aria-label]=\"resolvedToSource()\"\n (click)=\"moveLeft()\"\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 </div>\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/fesm2022/ngwr-tree.mjs
CHANGED
|
@@ -211,6 +211,13 @@ class WrTree {
|
|
|
211
211
|
expandLabel = readI18nText('tree.expand', 'Expand');
|
|
212
212
|
collapseLabel = readI18nText('tree.collapse', 'Collapse');
|
|
213
213
|
chipRemoveLabel = useI18nFormatter('tree.removeItem', 'Remove {{label}}');
|
|
214
|
+
/**
|
|
215
|
+
* The overflow chip past `maxTagCount`. It was the literal `+{{ extra }} more`
|
|
216
|
+
* in the template while `wr-select` — the same chip, the same string — read
|
|
217
|
+
* `select.more` from the catalog, so a Russian tree rendered
|
|
218
|
+
* `["Черновик", "+2 more"]`.
|
|
219
|
+
*/
|
|
220
|
+
chipMoreLabel = useI18nFormatter('tree.more', '+{{count}} more');
|
|
214
221
|
/** Flattened list of currently visible nodes — what the template renders. */
|
|
215
222
|
flat = computed(() => {
|
|
216
223
|
const expanded = new Set(this.expanded());
|
|
@@ -468,6 +475,12 @@ class WrTree {
|
|
|
468
475
|
let raf = 0;
|
|
469
476
|
let ro = null;
|
|
470
477
|
let el = null;
|
|
478
|
+
// `onCleanup` can run BEFORE the microtask below — the effect re-runs, or
|
|
479
|
+
// the component is destroyed, in the same task that scheduled it. It would
|
|
480
|
+
// then tear down nothing (`el` and `ro` are still null) and the
|
|
481
|
+
// continuation would go on to attach a scroll listener and a
|
|
482
|
+
// `ResizeObserver` that nothing is left to remove.
|
|
483
|
+
let cancelled = false;
|
|
471
484
|
const onScroll = () => {
|
|
472
485
|
if (raf)
|
|
473
486
|
return;
|
|
@@ -478,6 +491,8 @@ class WrTree {
|
|
|
478
491
|
});
|
|
479
492
|
};
|
|
480
493
|
queueMicrotask(() => {
|
|
494
|
+
if (cancelled)
|
|
495
|
+
return;
|
|
481
496
|
el = this.listElement;
|
|
482
497
|
if (!el)
|
|
483
498
|
return;
|
|
@@ -503,6 +518,7 @@ class WrTree {
|
|
|
503
518
|
}
|
|
504
519
|
});
|
|
505
520
|
onCleanup(() => {
|
|
521
|
+
cancelled = true;
|
|
506
522
|
if (raf)
|
|
507
523
|
cancelAnimationFrame(raf);
|
|
508
524
|
if (el)
|
|
@@ -799,11 +815,11 @@ class WrTree {
|
|
|
799
815
|
this.host.nativeElement.querySelector('.wr-tree__trigger')?.focus();
|
|
800
816
|
}
|
|
801
817
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTree, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
802
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrTree, isStandalone: true, selector: "wr-tree", inputs: { nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", 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 }, openOn: { classPropertyName: "openOn", publicName: "openOn", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, maxTagCount: { classPropertyName: "maxTagCount", publicName: "maxTagCount", isSignal: true, isRequired: false, transformFunction: null }, defaultExpandAll: { classPropertyName: "defaultExpandAll", publicName: "defaultExpandAll", isSignal: true, isRequired: false, transformFunction: null }, virtualScroll: { classPropertyName: "virtualScroll", publicName: "virtualScroll", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, viewportHeight: { classPropertyName: "viewportHeight", publicName: "viewportHeight", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange", expanded: "expandedChange", value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, viewQueries: [{ propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, read: TemplateRef, isSignal: true }], ngImport: i0, template: "@if (isOverlay()) {\n <button\n type=\"button\"\n class=\"wr-tree__trigger\"\n role=\"combobox\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'tree'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (click)=\"onTriggerClick()\"\n (blur)=\"onBlur()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-tree__chips\">\n @for (chip of visibleChips(); track chip.id) {\n <span class=\"wr-tree__chip\">\n <span class=\"wr-tree__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-tree__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.id, $event)\"\n (keydown.enter)=\"removeChip(chip.id, $event)\"\n (keydown.space)=\"removeChip(chip.id, $event)\"\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-tree__chip wr-tree__chip--more\">+{{ extra }} more</span>\n }\n </span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (singleLabel(); as label) {\n <span class=\"wr-tree__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (clearable() && hasSelection() && !disabled() && !readonly()) {\n <span\n class=\"wr-tree__clear\"\n role=\"button\"\n [attr.aria-label]=\"clearLabel()\"\n tabindex=\"-1\"\n (click)=\"clearSelection($event)\"\n (keydown.enter)=\"clearSelection($event)\"\n (keydown.space)=\"clearSelection($event)\"\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-tree__chevron\"\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=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Tree list lives inside the overlay panel. -->\n <ng-template #panelTpl>\n <div class=\"wr-tree-panel\" [class.wr-tree-panel--virtual]=\"virtualized()\" [attr.id]=\"panelId\">\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n </div>\n </ng-template>\n} @else {\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n}\n\n<!-- Shared row list \u2014 rendered inline (default) or projected into the\n overlay panel (when openOn=\"overlay\"). -->\n<ng-template #listTpl>\n <!-- Roving tabindex (non-virtual) / aria-activedescendant (virtual): the tree\n owns keyboard focus and routes arrow keys to its currently-focused row.\n The field's error state is bound here for the INLINE shape only \u2014 in\n overlay mode the trigger carries it, and the panel would otherwise\n describe the same error a second time. -->\n <ul\n class=\"wr-tree__list\"\n role=\"tree\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-invalid]=\"isOverlay() ? null : fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"isOverlay() ? null : fieldAria.describedBy()\"\n [class.wr-tree__list--virtual]=\"virtualized()\"\n [attr.tabindex]=\"virtualized() ? 0 : null\"\n [attr.aria-activedescendant]=\"activeRowId()\"\n [style.max-height]=\"virtualized() ? resolvedViewportHeight() : null\"\n [style.overflow-y]=\"virtualized() ? 'auto' : null\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (virtualized() && virtualWindow().padTop > 0) {\n <!-- `aria-setsize` / `aria-posinset` in BOTH shapes: the list is flat with no\n `role=\"group\"` either way, so the set a row belongs to is never expressed\n by the DOM. Gated behind the virtual flag, the same tree announced a\n position with windowing on and nothing with it off. -->\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padTop\"\n ></li>\n }\n @for (entry of windowRows(); track entry.node.id) {\n <!-- Click handler lives on focusable rows; keyboard handling is owned\n by the parent <ul> via the roving-tabindex pattern. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <li\n class=\"wr-tree__row\"\n role=\"treeitem\"\n [attr.id]=\"virtualized() ? rowId(entry.index) : null\"\n [class.wr-tree__row--selected]=\"selectedSet().has(entry.node.id)\"\n [class.wr-tree__row--active]=\"virtualized() && entry.index === cursorIndex()\"\n [class.wr-tree__row--disabled]=\"entry.node.disabled\"\n [attr.aria-level]=\"entry.depth + 1\"\n [attr.aria-setsize]=\"entry.setsize\"\n [attr.aria-posinset]=\"entry.posinset\"\n [attr.aria-expanded]=\"entry.hasChildren ? expandedSet().has(entry.node.id) : null\"\n [attr.aria-selected]=\"selectionMode() === 'none' ? null : selectedSet().has(entry.node.id)\"\n [attr.aria-disabled]=\"entry.node.disabled || disabled() || null\"\n [attr.data-tree-index]=\"entry.index\"\n [attr.tabindex]=\"virtualized() ? null : entry.index === cursorIndex() ? 0 : -1\"\n [style.--wr-tree-depth]=\"entry.depth\"\n (click)=\"onRowClick(entry, $event)\"\n >\n @if (entry.hasChildren) {\n <button\n type=\"button\"\n class=\"wr-tree__toggle\"\n tabindex=\"-1\"\n [attr.aria-label]=\"expandedSet().has(entry.node.id) ? collapseLabel() : expandLabel()\"\n (click)=\"onToggleClick(entry.node, $event)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M3 1 L7 5 L3 9 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n } @else {\n <span class=\"wr-tree__toggle wr-tree__toggle--leaf\" aria-hidden=\"true\"></span>\n }\n <span class=\"wr-tree__label\">{{ entry.node.label }}</span>\n </li>\n }\n @if (virtualized() && virtualWindow().padBottom > 0) {\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padBottom\"\n ></li>\n }\n </ul>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
818
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: WrTree, isStandalone: true, selector: "wr-tree", inputs: { nodes: { classPropertyName: "nodes", publicName: "nodes", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", 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 }, openOn: { classPropertyName: "openOn", publicName: "openOn", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, maxTagCount: { classPropertyName: "maxTagCount", publicName: "maxTagCount", isSignal: true, isRequired: false, transformFunction: null }, defaultExpandAll: { classPropertyName: "defaultExpandAll", publicName: "defaultExpandAll", isSignal: true, isRequired: false, transformFunction: null }, virtualScroll: { classPropertyName: "virtualScroll", publicName: "virtualScroll", isSignal: true, isRequired: false, transformFunction: null }, rowHeight: { classPropertyName: "rowHeight", publicName: "rowHeight", isSignal: true, isRequired: false, transformFunction: null }, viewportHeight: { classPropertyName: "viewportHeight", publicName: "viewportHeight", isSignal: true, isRequired: false, transformFunction: null }, overscan: { classPropertyName: "overscan", publicName: "overscan", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selectedChange", expanded: "expandedChange", value: "valueChange", touch: "touch" }, host: { properties: { "class": "classes()" } }, viewQueries: [{ propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, read: TemplateRef, isSignal: true }], ngImport: i0, template: "@if (isOverlay()) {\n <button\n type=\"button\"\n class=\"wr-tree__trigger\"\n role=\"combobox\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'tree'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (click)=\"onTriggerClick()\"\n (blur)=\"onBlur()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-tree__chips\">\n @for (chip of visibleChips(); track chip.id) {\n <span class=\"wr-tree__chip\">\n <span class=\"wr-tree__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-tree__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.id, $event)\"\n (keydown.enter)=\"removeChip(chip.id, $event)\"\n (keydown.space)=\"removeChip(chip.id, $event)\"\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-tree__chip wr-tree__chip--more\">{{ chipMoreLabel({ count: extra }) }}</span>\n }\n </span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (singleLabel(); as label) {\n <span class=\"wr-tree__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (clearable() && hasSelection() && !disabled() && !readonly()) {\n <span\n class=\"wr-tree__clear\"\n role=\"button\"\n [attr.aria-label]=\"clearLabel()\"\n tabindex=\"-1\"\n (click)=\"clearSelection($event)\"\n (keydown.enter)=\"clearSelection($event)\"\n (keydown.space)=\"clearSelection($event)\"\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-tree__chevron\"\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=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Tree list lives inside the overlay panel. -->\n <ng-template #panelTpl>\n <div class=\"wr-tree-panel\" [class.wr-tree-panel--virtual]=\"virtualized()\" [attr.id]=\"panelId\">\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n </div>\n </ng-template>\n} @else {\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n}\n\n<!-- Shared row list \u2014 rendered inline (default) or projected into the\n overlay panel (when openOn=\"overlay\"). -->\n<ng-template #listTpl>\n <!-- Roving tabindex (non-virtual) / aria-activedescendant (virtual): the tree\n owns keyboard focus and routes arrow keys to its currently-focused row.\n The field's error state is bound here for the INLINE shape only \u2014 in\n overlay mode the trigger carries it, and the panel would otherwise\n describe the same error a second time. -->\n <ul\n class=\"wr-tree__list\"\n role=\"tree\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-invalid]=\"isOverlay() ? null : fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"isOverlay() ? null : fieldAria.describedBy()\"\n [class.wr-tree__list--virtual]=\"virtualized()\"\n [attr.tabindex]=\"virtualized() ? 0 : null\"\n [attr.aria-activedescendant]=\"activeRowId()\"\n [style.max-height]=\"virtualized() ? resolvedViewportHeight() : null\"\n [style.overflow-y]=\"virtualized() ? 'auto' : null\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (virtualized() && virtualWindow().padTop > 0) {\n <!-- `aria-setsize` / `aria-posinset` in BOTH shapes: the list is flat with no\n `role=\"group\"` either way, so the set a row belongs to is never expressed\n by the DOM. Gated behind the virtual flag, the same tree announced a\n position with windowing on and nothing with it off. -->\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padTop\"\n ></li>\n }\n @for (entry of windowRows(); track entry.node.id) {\n <!-- Click handler lives on focusable rows; keyboard handling is owned\n by the parent <ul> via the roving-tabindex pattern. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <li\n class=\"wr-tree__row\"\n role=\"treeitem\"\n [attr.id]=\"virtualized() ? rowId(entry.index) : null\"\n [class.wr-tree__row--selected]=\"selectedSet().has(entry.node.id)\"\n [class.wr-tree__row--active]=\"virtualized() && entry.index === cursorIndex()\"\n [class.wr-tree__row--disabled]=\"entry.node.disabled\"\n [attr.aria-level]=\"entry.depth + 1\"\n [attr.aria-setsize]=\"entry.setsize\"\n [attr.aria-posinset]=\"entry.posinset\"\n [attr.aria-expanded]=\"entry.hasChildren ? expandedSet().has(entry.node.id) : null\"\n [attr.aria-selected]=\"selectionMode() === 'none' ? null : selectedSet().has(entry.node.id)\"\n [attr.aria-disabled]=\"entry.node.disabled || disabled() || null\"\n [attr.data-tree-index]=\"entry.index\"\n [attr.tabindex]=\"virtualized() ? null : entry.index === cursorIndex() ? 0 : -1\"\n [style.--wr-tree-depth]=\"entry.depth\"\n (click)=\"onRowClick(entry, $event)\"\n >\n @if (entry.hasChildren) {\n <button\n type=\"button\"\n class=\"wr-tree__toggle\"\n tabindex=\"-1\"\n [attr.aria-label]=\"expandedSet().has(entry.node.id) ? collapseLabel() : expandLabel()\"\n (click)=\"onToggleClick(entry.node, $event)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M3 1 L7 5 L3 9 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n } @else {\n <span class=\"wr-tree__toggle wr-tree__toggle--leaf\" aria-hidden=\"true\"></span>\n }\n <span class=\"wr-tree__label\">{{ entry.node.label }}</span>\n </li>\n }\n @if (virtualized() && virtualWindow().padBottom > 0) {\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padBottom\"\n ></li>\n }\n </ul>\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
803
819
|
}
|
|
804
820
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: WrTree, decorators: [{
|
|
805
821
|
type: Component,
|
|
806
|
-
args: [{ selector: 'wr-tree', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [NgTemplateOutlet], template: "@if (isOverlay()) {\n <button\n type=\"button\"\n class=\"wr-tree__trigger\"\n role=\"combobox\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'tree'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (click)=\"onTriggerClick()\"\n (blur)=\"onBlur()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-tree__chips\">\n @for (chip of visibleChips(); track chip.id) {\n <span class=\"wr-tree__chip\">\n <span class=\"wr-tree__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-tree__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.id, $event)\"\n (keydown.enter)=\"removeChip(chip.id, $event)\"\n (keydown.space)=\"removeChip(chip.id, $event)\"\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-tree__chip wr-tree__chip--more\"
|
|
822
|
+
args: [{ selector: 'wr-tree', encapsulation: ViewEncapsulation.None, host: { '[class]': 'classes()' }, imports: [NgTemplateOutlet], template: "@if (isOverlay()) {\n <button\n type=\"button\"\n class=\"wr-tree__trigger\"\n role=\"combobox\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [disabled]=\"disabled()\"\n [attr.aria-haspopup]=\"'tree'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-readonly]=\"readonly() || null\"\n [attr.aria-invalid]=\"fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"fieldAria.describedBy()\"\n (click)=\"onTriggerClick()\"\n (blur)=\"onBlur()\"\n >\n @if (isMulti()) {\n @if (hasSelection()) {\n <span class=\"wr-tree__chips\">\n @for (chip of visibleChips(); track chip.id) {\n <span class=\"wr-tree__chip\">\n <span class=\"wr-tree__chip-label\">{{ chip.label }}</span>\n <span\n class=\"wr-tree__chip-remove\"\n role=\"button\"\n [attr.aria-label]=\"chipRemoveLabel({ label: chip.label })\"\n tabindex=\"-1\"\n (click)=\"removeChip(chip.id, $event)\"\n (keydown.enter)=\"removeChip(chip.id, $event)\"\n (keydown.space)=\"removeChip(chip.id, $event)\"\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n </span>\n }\n @if (hiddenChipCount(); as extra) {\n <span class=\"wr-tree__chip wr-tree__chip--more\">{{ chipMoreLabel({ count: extra }) }}</span>\n }\n </span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n } @else {\n @if (singleLabel(); as label) {\n <span class=\"wr-tree__value\">{{ label }}</span>\n } @else {\n <span class=\"wr-tree__placeholder\">{{ resolvedPlaceholder() }}</span>\n }\n }\n\n @if (clearable() && hasSelection() && !disabled() && !readonly()) {\n <span\n class=\"wr-tree__clear\"\n role=\"button\"\n [attr.aria-label]=\"clearLabel()\"\n tabindex=\"-1\"\n (click)=\"clearSelection($event)\"\n (keydown.enter)=\"clearSelection($event)\"\n (keydown.space)=\"clearSelection($event)\"\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=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </span>\n }\n\n <svg\n class=\"wr-icon__svg wr-tree__chevron\"\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=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Tree list lives inside the overlay panel. -->\n <ng-template #panelTpl>\n <div class=\"wr-tree-panel\" [class.wr-tree-panel--virtual]=\"virtualized()\" [attr.id]=\"panelId\">\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n </div>\n </ng-template>\n} @else {\n <ng-container [ngTemplateOutlet]=\"listTpl\" />\n}\n\n<!-- Shared row list \u2014 rendered inline (default) or projected into the\n overlay panel (when openOn=\"overlay\"). -->\n<ng-template #listTpl>\n <!-- Roving tabindex (non-virtual) / aria-activedescendant (virtual): the tree\n owns keyboard focus and routes arrow keys to its currently-focused row.\n The field's error state is bound here for the INLINE shape only \u2014 in\n overlay mode the trigger carries it, and the panel would otherwise\n describe the same error a second time. -->\n <ul\n class=\"wr-tree__list\"\n role=\"tree\"\n [attr.aria-multiselectable]=\"isMulti() ? 'true' : null\"\n [attr.aria-invalid]=\"isOverlay() ? null : fieldAria.ariaInvalid()\"\n [attr.aria-describedby]=\"isOverlay() ? null : fieldAria.describedBy()\"\n [class.wr-tree__list--virtual]=\"virtualized()\"\n [attr.tabindex]=\"virtualized() ? 0 : null\"\n [attr.aria-activedescendant]=\"activeRowId()\"\n [style.max-height]=\"virtualized() ? resolvedViewportHeight() : null\"\n [style.overflow-y]=\"virtualized() ? 'auto' : null\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (virtualized() && virtualWindow().padTop > 0) {\n <!-- `aria-setsize` / `aria-posinset` in BOTH shapes: the list is flat with no\n `role=\"group\"` either way, so the set a row belongs to is never expressed\n by the DOM. Gated behind the virtual flag, the same tree announced a\n position with windowing on and nothing with it off. -->\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padTop\"\n ></li>\n }\n @for (entry of windowRows(); track entry.node.id) {\n <!-- Click handler lives on focusable rows; keyboard handling is owned\n by the parent <ul> via the roving-tabindex pattern. -->\n <!-- eslint-disable-next-line @angular-eslint/template/click-events-have-key-events -->\n <li\n class=\"wr-tree__row\"\n role=\"treeitem\"\n [attr.id]=\"virtualized() ? rowId(entry.index) : null\"\n [class.wr-tree__row--selected]=\"selectedSet().has(entry.node.id)\"\n [class.wr-tree__row--active]=\"virtualized() && entry.index === cursorIndex()\"\n [class.wr-tree__row--disabled]=\"entry.node.disabled\"\n [attr.aria-level]=\"entry.depth + 1\"\n [attr.aria-setsize]=\"entry.setsize\"\n [attr.aria-posinset]=\"entry.posinset\"\n [attr.aria-expanded]=\"entry.hasChildren ? expandedSet().has(entry.node.id) : null\"\n [attr.aria-selected]=\"selectionMode() === 'none' ? null : selectedSet().has(entry.node.id)\"\n [attr.aria-disabled]=\"entry.node.disabled || disabled() || null\"\n [attr.data-tree-index]=\"entry.index\"\n [attr.tabindex]=\"virtualized() ? null : entry.index === cursorIndex() ? 0 : -1\"\n [style.--wr-tree-depth]=\"entry.depth\"\n (click)=\"onRowClick(entry, $event)\"\n >\n @if (entry.hasChildren) {\n <button\n type=\"button\"\n class=\"wr-tree__toggle\"\n tabindex=\"-1\"\n [attr.aria-label]=\"expandedSet().has(entry.node.id) ? collapseLabel() : expandLabel()\"\n (click)=\"onToggleClick(entry.node, $event)\"\n >\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" aria-hidden=\"true\">\n <path d=\"M3 1 L7 5 L3 9 Z\" fill=\"currentColor\" />\n </svg>\n </button>\n } @else {\n <span class=\"wr-tree__toggle wr-tree__toggle--leaf\" aria-hidden=\"true\"></span>\n }\n <span class=\"wr-tree__label\">{{ entry.node.label }}</span>\n </li>\n }\n @if (virtualized() && virtualWindow().padBottom > 0) {\n <li\n class=\"wr-tree__spacer\"\n role=\"presentation\"\n aria-hidden=\"true\"\n [style.height.px]=\"virtualWindow().padBottom\"\n ></li>\n }\n </ul>\n</ng-template>\n" }]
|
|
807
823
|
}], ctorParameters: () => [], propDecorators: { nodes: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodes", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }, { type: i0.Output, args: ["selectedChange"] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }, { type: i0.Output, args: ["expandedChange"] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], openOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOn", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], maxTagCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTagCount", required: false }] }], defaultExpandAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultExpandAll", required: false }] }], virtualScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScroll", required: false }] }], rowHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowHeight", required: false }] }], viewportHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewportHeight", required: false }] }], overscan: [{ type: i0.Input, args: [{ isSignal: true, alias: "overscan", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], touch: [{ type: i0.Output, args: ["touch"] }], panelTpl: [{ type: i0.ViewChild, args: ['panelTpl', { ...{ read: TemplateRef }, isSignal: true }] }] } });
|
|
808
824
|
|
|
809
825
|
/**
|