@cdevhub/ngx-tw 0.4.0 → 0.6.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 +78 -15
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs +16 -5
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs +26 -15
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-badge.mjs +17 -5
- package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +27 -6
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-button.mjs +23 -11
- package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +5 -5
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs +3 -3
- package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs +427 -122
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-card.mjs +21 -11
- package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs +215 -96
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +108 -64
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs +18 -6
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +55 -30
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs +218 -0
- package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs +236 -211
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs +200 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +34 -7
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-core.mjs +135 -15
- package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs +203 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +160 -102
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs +209 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +182 -107
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-dialog-dialog-renderer-DoIhoV3d.mjs +198 -0
- package/fesm2022/cdevhub-ngx-tw-dialog-dialog-renderer-DoIhoV3d.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs +122 -0
- package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs +199 -220
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs +149 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs +57 -67
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +42 -24
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs +114 -21
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-icon.mjs +16 -6
- package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-input.mjs +52 -61
- package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-item.mjs +18 -3
- package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs +168 -0
- package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-menu.mjs +58 -21
- package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs +34 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs +95 -45
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-popover.mjs +81 -32
- package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-radio.mjs +131 -98
- package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +207 -42
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-select-testing.mjs +165 -0
- package/fesm2022/cdevhub-ngx-tw-select-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-select.mjs +386 -210
- package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs +256 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs +129 -0
- package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs +197 -278
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-slider.mjs +89 -54
- package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sort.mjs +122 -17
- package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-split.mjs +72 -19
- package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stat.mjs +63 -25
- package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs +35 -8
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-switch.mjs +54 -48
- package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +12 -15
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-table.mjs +74 -13
- package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs +84 -30
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs +172 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs +107 -71
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs +41 -9
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-theme.mjs +257 -45
- package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs +201 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +184 -77
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs +110 -27
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs +616 -0
- package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-toast.mjs +556 -945
- package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +102 -15
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs +219 -0
- package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs +88 -69
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tree.mjs +68 -19
- package/fesm2022/cdevhub-ngx-tw-tree.mjs.map +1 -1
- package/index.json +1 -1
- package/package.json +63 -2
- package/theme/_base.css +13 -0
- package/theme/_dark.css +110 -36
- package/theme/_high-contrast-dark.css +367 -0
- package/theme/_high-contrast.css +13 -2
- package/theme/_light.css +276 -0
- package/theme/_semantic.css +47 -12
- package/theme/_typography.css +17 -0
- package/theme/index.css +86 -7
- package/types/cdevhub-ngx-tw-accordion.d.ts +12 -4
- package/types/cdevhub-ngx-tw-avatar.d.ts +10 -3
- package/types/cdevhub-ngx-tw-button.d.ts +1 -0
- package/types/cdevhub-ngx-tw-calendar-luxon.d.ts +2 -2
- package/types/cdevhub-ngx-tw-calendar-testing.d.ts +1 -1
- package/types/cdevhub-ngx-tw-calendar.d.ts +179 -73
- package/types/cdevhub-ngx-tw-card.d.ts +11 -3
- package/types/cdevhub-ngx-tw-carousel.d.ts +89 -20
- package/types/cdevhub-ngx-tw-checkbox.d.ts +40 -12
- package/types/cdevhub-ngx-tw-code-block.d.ts +11 -2
- package/types/cdevhub-ngx-tw-collapsible.d.ts +32 -5
- package/types/cdevhub-ngx-tw-combobox-testing.d.ts +157 -0
- package/types/cdevhub-ngx-tw-combobox.d.ts +94 -53
- package/types/cdevhub-ngx-tw-command-palette-testing.d.ts +136 -0
- package/types/cdevhub-ngx-tw-command-palette.d.ts +33 -5
- package/types/cdevhub-ngx-tw-core.d.ts +152 -21
- package/types/cdevhub-ngx-tw-date-picker-testing.d.ts +141 -0
- package/types/cdevhub-ngx-tw-date-picker.d.ts +37 -29
- package/types/cdevhub-ngx-tw-date-range-picker-testing.d.ts +148 -0
- package/types/cdevhub-ngx-tw-date-range-picker.d.ts +36 -29
- package/types/cdevhub-ngx-tw-dialog-testing.d.ts +98 -0
- package/types/cdevhub-ngx-tw-dialog.d.ts +89 -19
- package/types/cdevhub-ngx-tw-file-upload-testing.d.ts +120 -0
- package/types/cdevhub-ngx-tw-file-upload.d.ts +8 -10
- package/types/cdevhub-ngx-tw-flip-card.d.ts +18 -11
- package/types/cdevhub-ngx-tw-form-field.d.ts +19 -2
- package/types/cdevhub-ngx-tw-icon.d.ts +3 -3
- package/types/cdevhub-ngx-tw-input.d.ts +5 -8
- package/types/cdevhub-ngx-tw-item.d.ts +2 -0
- package/types/cdevhub-ngx-tw-menu-testing.d.ts +131 -0
- package/types/cdevhub-ngx-tw-menu.d.ts +2 -2
- package/types/cdevhub-ngx-tw-number-input.d.ts +20 -0
- package/types/cdevhub-ngx-tw-paginator.d.ts +58 -24
- package/types/cdevhub-ngx-tw-popover.d.ts +41 -10
- package/types/cdevhub-ngx-tw-progress-bar.d.ts +1 -1
- package/types/cdevhub-ngx-tw-radio.d.ts +68 -25
- package/types/cdevhub-ngx-tw-segmented-control.d.ts +79 -16
- package/types/cdevhub-ngx-tw-select-testing.d.ts +114 -0
- package/types/cdevhub-ngx-tw-select.d.ts +104 -33
- package/types/cdevhub-ngx-tw-sheet-testing.d.ts +104 -0
- package/types/cdevhub-ngx-tw-sheet.d.ts +96 -21
- package/types/cdevhub-ngx-tw-slider.d.ts +79 -25
- package/types/cdevhub-ngx-tw-sort.d.ts +45 -4
- package/types/cdevhub-ngx-tw-split.d.ts +32 -2
- package/types/cdevhub-ngx-tw-stat.d.ts +14 -4
- package/types/cdevhub-ngx-tw-stepper.d.ts +16 -2
- package/types/cdevhub-ngx-tw-switch.d.ts +35 -13
- package/types/cdevhub-ngx-tw-tab-nav.d.ts +7 -6
- package/types/cdevhub-ngx-tw-table.d.ts +53 -10
- package/types/cdevhub-ngx-tw-tabs.d.ts +41 -10
- package/types/cdevhub-ngx-tw-tags-input-testing.d.ts +135 -0
- package/types/cdevhub-ngx-tw-tags-input.d.ts +26 -11
- package/types/cdevhub-ngx-tw-textarea.d.ts +3 -2
- package/types/cdevhub-ngx-tw-theme.d.ts +245 -39
- package/types/cdevhub-ngx-tw-time-picker-testing.d.ts +129 -0
- package/types/cdevhub-ngx-tw-time-picker.d.ts +76 -19
- package/types/cdevhub-ngx-tw-timeline.d.ts +36 -12
- package/types/cdevhub-ngx-tw-toast.d.ts +61 -12
- package/types/cdevhub-ngx-tw-tooltip.d.ts +28 -6
- package/types/cdevhub-ngx-tw-transfer-testing.d.ts +152 -0
- package/types/cdevhub-ngx-tw-transfer.d.ts +21 -8
- package/types/cdevhub-ngx-tw-tree.d.ts +6 -1
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { NgTemplateOutlet } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
3
|
import { inject, TemplateRef, Directive, input, output, contentChild, ElementRef, Injector, DestroyRef, signal, computed, linkedSignal, afterNextRender, isDevMode, forwardRef, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
4
|
-
import { NgControl
|
|
4
|
+
import { NgControl } from '@angular/forms';
|
|
5
5
|
import { CdkListbox, CdkOption } from '@angular/cdk/listbox';
|
|
6
6
|
import { FocusMonitor, LiveAnnouncer } from '@angular/cdk/a11y';
|
|
7
7
|
import { Platform } from '@angular/cdk/platform';
|
|
8
8
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
9
|
-
import { merge } from 'rxjs';
|
|
10
9
|
import { tv } from 'tailwind-variants';
|
|
11
|
-
import {
|
|
10
|
+
import { wireErrorState } from '@cdevhub/ngx-tw/core';
|
|
12
11
|
import { FormFieldControl, TW_FORM_FIELD_CONTROL } from '@cdevhub/ngx-tw/form-field';
|
|
13
12
|
import { CheckboxComponent } from '@cdevhub/ngx-tw/checkbox';
|
|
14
13
|
import { InputDirective } from '@cdevhub/ngx-tw/input';
|
|
@@ -65,6 +64,12 @@ const DISPLAY_DEFAULTS = {
|
|
|
65
64
|
};
|
|
66
65
|
/** Replaces `{name}` tokens in a template with values from `vars`. */
|
|
67
66
|
function formatLabel(template, vars) {
|
|
67
|
+
// Defence in depth. `resolvedLabels()` already filters explicitly-undefined
|
|
68
|
+
// consumer keys, so a non-string cannot reach here through the public
|
|
69
|
+
// `labels` input today. The guard exists so a future regression in that
|
|
70
|
+
// merge degrades to a missing label instead of throwing.
|
|
71
|
+
if (typeof template !== 'string')
|
|
72
|
+
return '';
|
|
68
73
|
return template.replace(/\{(\w+)\}/g, (match, key) => key in vars ? String(vars[key]) : match);
|
|
69
74
|
}
|
|
70
75
|
// ── tv() config ───────────────────────────────────────────────────────
|
|
@@ -97,6 +102,13 @@ const transferVariants = tv({
|
|
|
97
102
|
// size; not the [twButton] directive (its size padding would fight a square
|
|
98
103
|
// override across two class bindings).
|
|
99
104
|
moveButton: 'inline-flex items-center justify-center shrink-0 rounded-md border border-border bg-surface text-fg-muted cursor-pointer transition-colors duration-200 motion-reduce:transition-none hover:bg-surface-muted hover:text-fg hover:border-border-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500 disabled:opacity-50 disabled:pointer-events-none disabled:cursor-not-allowed',
|
|
105
|
+
// Glyph inside the square move button. Glyph sub-scale saturating at
|
|
106
|
+
// `lg`: 3 / 4 / 5 / 6 / 6, matching `optionCheck`. Previously 4/4/5/5/5,
|
|
107
|
+
// which left three of the five advertised steps dead. `xl` stops at
|
|
108
|
+
// 24px rather than the scale's 32px because the glyph lives inside a
|
|
109
|
+
// 36px `moveButton` (the square-interactive scale saturates at `lg`
|
|
110
|
+
// too, per CLAUDE.md) — a 32px glyph in a 36px target leaves no optical
|
|
111
|
+
// padding. Raising one means raising the other.
|
|
100
112
|
moveIcon: 'shrink-0',
|
|
101
113
|
empty: 'flex items-center justify-center p-4 text-sm text-fg-subtle text-center',
|
|
102
114
|
},
|
|
@@ -106,9 +118,9 @@ const transferVariants = tv({
|
|
|
106
118
|
panelHeader: 'px-2 py-1',
|
|
107
119
|
search: 'px-2 py-1',
|
|
108
120
|
option: 'px-2 py-1 text-xs gap-1.5',
|
|
109
|
-
optionCheck: 'size-
|
|
121
|
+
optionCheck: 'size-3',
|
|
110
122
|
moveButton: 'size-6',
|
|
111
|
-
moveIcon: 'size-
|
|
123
|
+
moveIcon: 'size-3',
|
|
112
124
|
empty: 'text-xs',
|
|
113
125
|
},
|
|
114
126
|
sm: {
|
|
@@ -131,17 +143,17 @@ const transferVariants = tv({
|
|
|
131
143
|
panelHeader: 'px-5 py-2.5',
|
|
132
144
|
search: 'px-4 py-2.5',
|
|
133
145
|
option: 'px-4 py-2.5 text-base',
|
|
134
|
-
optionCheck: 'size-
|
|
146
|
+
optionCheck: 'size-6',
|
|
135
147
|
moveButton: 'size-9',
|
|
136
|
-
moveIcon: 'size-
|
|
148
|
+
moveIcon: 'size-6',
|
|
137
149
|
},
|
|
138
150
|
xl: {
|
|
139
151
|
panelHeader: 'px-6 py-3',
|
|
140
152
|
search: 'px-4 py-3',
|
|
141
153
|
option: 'px-4 py-3 text-base',
|
|
142
|
-
optionCheck: 'size-
|
|
154
|
+
optionCheck: 'size-6',
|
|
143
155
|
moveButton: 'size-9',
|
|
144
|
-
moveIcon: 'size-
|
|
156
|
+
moveIcon: 'size-6',
|
|
145
157
|
},
|
|
146
158
|
},
|
|
147
159
|
checked: {
|
|
@@ -233,13 +245,13 @@ class TransferComponent extends FormFieldControl {
|
|
|
233
245
|
/** Reserved for control identification (e.g. analytics, future native-submission parity). The transfer has no single native value element, so it is not currently reflected to the DOM. Defaults to `undefined`. */
|
|
234
246
|
name = input(undefined, /* @ts-ignore */
|
|
235
247
|
...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
236
|
-
/** Id on the host element. Auto-generated as `tw-transfer-N` when unset. Used by the form-field's `<label for>` association. */
|
|
248
|
+
/** Id on the host element. Auto-generated as `tw-transfer-N` when unset. Used by the form-field's `<label for>` association. Alias: `id`. */
|
|
237
249
|
idInput = input(undefined, { ...(ngDevMode ? { debugName: "idInput" } : /* istanbul ignore next */ {}), alias: 'id' });
|
|
238
250
|
/** Accessible name applied to the control when no visible label is wired. Mirrored to `aria-label`. Defaults to `undefined`. */
|
|
239
251
|
ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
|
|
240
252
|
/** ID of an external element that labels the control. Mirrored to `aria-labelledby`. Defaults to `undefined`. */
|
|
241
253
|
ariaLabelledby = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabelledby" } : /* istanbul ignore next */ {}), alias: 'aria-labelledby' });
|
|
242
|
-
/** ID of an external element that describes the control. The form-field merges its hint / error ids alongside. Defaults to `undefined`. */
|
|
254
|
+
/** ID of an external element that describes the control. The form-field merges its hint / error ids alongside. Defaults to `undefined`. Alias: `aria-describedby`. */
|
|
243
255
|
ariaDescribedby = input(undefined, { ...(ngDevMode ? { debugName: "ariaDescribedby" } : /* istanbul ignore next */ {}), alias: 'aria-describedby' });
|
|
244
256
|
/** Per-instance override of the {@link ErrorStateMatcher}. When omitted, uses the `TW_ERROR_STATE_MATCHER` token's value. Defaults to `undefined`. */
|
|
245
257
|
errorStateMatcher = input(undefined, /* @ts-ignore */
|
|
@@ -260,9 +272,6 @@ class TransferComponent extends FormFieldControl {
|
|
|
260
272
|
focusMonitor = inject(FocusMonitor);
|
|
261
273
|
liveAnnouncer = inject(LiveAnnouncer);
|
|
262
274
|
ngControl = inject(NgControl, { optional: true, self: true });
|
|
263
|
-
parentForm = inject(NgForm, { optional: true });
|
|
264
|
-
parentFormGroup = inject(FormGroupDirective, { optional: true });
|
|
265
|
-
defaultMatcher = inject(TW_ERROR_STATE_MATCHER);
|
|
266
275
|
platform = inject(Platform);
|
|
267
276
|
// ── Identity ──
|
|
268
277
|
uid = nextId++;
|
|
@@ -283,10 +292,13 @@ class TransferComponent extends FormFieldControl {
|
|
|
283
292
|
...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
284
293
|
_focused = signal(false, /* @ts-ignore */
|
|
285
294
|
...(ngDevMode ? [{ debugName: "_focused" }] : /* istanbul ignore next */ []));
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
295
|
+
/** @internal Shared `errorState` / `required` / `errors` derivation — see `wireErrorState`. */
|
|
296
|
+
errorWiring = wireErrorState({
|
|
297
|
+
ngControl: () => this.ngControl,
|
|
298
|
+
matcher: () => this.errorStateMatcher(),
|
|
299
|
+
required: () => this.requiredInput(),
|
|
300
|
+
track: [() => this._focused()],
|
|
301
|
+
});
|
|
290
302
|
describedByIdsSignal = signal([], /* @ts-ignore */
|
|
291
303
|
...(ngDevMode ? [{ debugName: "describedByIdsSignal" }] : /* istanbul ignore next */ []));
|
|
292
304
|
labelledByIdsSignal = signal([], /* @ts-ignore */
|
|
@@ -296,16 +308,32 @@ class TransferComponent extends FormFieldControl {
|
|
|
296
308
|
/** @internal Stable comparator for `[cdkListboxCompareWith]`. Keys are identity-comparable. */
|
|
297
309
|
compareKeys = (a, b) => Object.is(a, b);
|
|
298
310
|
// ── Resolved config ──
|
|
299
|
-
/**
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
311
|
+
/**
|
|
312
|
+
* @internal Resolved label record.
|
|
313
|
+
*
|
|
314
|
+
* Explicitly-undefined keys are dropped before merging. Root `tsconfig.json`
|
|
315
|
+
* does not set `exactOptionalPropertyTypes`, so
|
|
316
|
+
* `[labels]="{ moveAnnouncement: i18n.moved }"` type-checks even when the
|
|
317
|
+
* i18n bundle has no such key — and a plain spread would then overwrite the
|
|
318
|
+
* default with `undefined`, which reaches `formatLabel()` and throws
|
|
319
|
+
* `Cannot read properties of undefined (reading 'replace')`. Same filter
|
|
320
|
+
* `table.ts` and `timeline.ts` already use; the `Required<>` annotation only
|
|
321
|
+
* tells the truth once the filter is in place.
|
|
322
|
+
*/
|
|
323
|
+
resolvedLabels = computed(() => {
|
|
324
|
+
const overrides = Object.fromEntries(Object.entries(this.labels() ?? {}).filter(([, value]) => value !== undefined));
|
|
325
|
+
return { ...LABELS_DEFAULTS, ...overrides };
|
|
326
|
+
}, /* @ts-ignore */
|
|
304
327
|
...(ngDevMode ? [{ debugName: "resolvedLabels" }] : /* istanbul ignore next */ []));
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
328
|
+
/**
|
|
329
|
+
* @internal Resolved display config. Same explicitly-undefined filter as
|
|
330
|
+
* `resolvedLabels` — a plain spread let `[display]="{ listHeight: undefined }"`
|
|
331
|
+
* reach the list's inline `height` as `undefined`, collapsing the panes.
|
|
332
|
+
*/
|
|
333
|
+
resolvedDisplay = computed(() => {
|
|
334
|
+
const overrides = Object.fromEntries(Object.entries(this.display() ?? {}).filter(([, value]) => value !== undefined));
|
|
335
|
+
return { ...DISPLAY_DEFAULTS, ...overrides };
|
|
336
|
+
}, /* @ts-ignore */
|
|
309
337
|
...(ngDevMode ? [{ debugName: "resolvedDisplay" }] : /* istanbul ignore next */ []));
|
|
310
338
|
// Behaviour is resolved inline (not via a `Required<>` const) because `filterFn`
|
|
311
339
|
// is legitimately optional and both predicates are generic over `T`.
|
|
@@ -701,30 +729,11 @@ class TransferComponent extends FormFieldControl {
|
|
|
701
729
|
disabled = computed(() => this.disabledInput() || this.cvaDisabled(), /* @ts-ignore */
|
|
702
730
|
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
703
731
|
/** @internal */
|
|
704
|
-
required =
|
|
705
|
-
this._ngControlRev();
|
|
706
|
-
if (this.requiredInput())
|
|
707
|
-
return true;
|
|
708
|
-
return !!this.ngControl?.control?.hasValidator(Validators.required);
|
|
709
|
-
}, /* @ts-ignore */
|
|
710
|
-
...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
732
|
+
required = this.errorWiring.required;
|
|
711
733
|
/** @internal */
|
|
712
|
-
errorState =
|
|
713
|
-
this._ngControlRev();
|
|
714
|
-
this._formSubmitRev();
|
|
715
|
-
this._focused();
|
|
716
|
-
const matcher = this.errorStateMatcher() ?? this.defaultMatcher;
|
|
717
|
-
const form = this.parentFormGroup ??
|
|
718
|
-
this.parentForm;
|
|
719
|
-
return matcher.isErrorState(this.ngControl?.control ?? null, form);
|
|
720
|
-
}, /* @ts-ignore */
|
|
721
|
-
...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
|
|
734
|
+
errorState = this.errorWiring.errorState;
|
|
722
735
|
/** @internal Active validation errors from the bound control, for `[twError match="…"]` filtering. */
|
|
723
|
-
errors =
|
|
724
|
-
this._ngControlRev();
|
|
725
|
-
return this.ngControl?.control?.errors ?? null;
|
|
726
|
-
}, /* @ts-ignore */
|
|
727
|
-
...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
736
|
+
errors = this.errorWiring.errors;
|
|
728
737
|
/** @internal */
|
|
729
738
|
controlType = 'transfer';
|
|
730
739
|
/** @internal */
|
|
@@ -804,29 +813,15 @@ class TransferComponent extends FormFieldControl {
|
|
|
804
813
|
this._focused.set(!!origin);
|
|
805
814
|
if (wasFocused && !origin) {
|
|
806
815
|
this.onTouched();
|
|
807
|
-
this.
|
|
816
|
+
this.errorWiring.bump();
|
|
808
817
|
}
|
|
809
818
|
});
|
|
810
819
|
this.destroyRef.onDestroy(() => this.focusMonitor.stopMonitoring(this.elementRef));
|
|
811
820
|
// React to status/value changes on the bound control so errorState recomputes.
|
|
812
|
-
|
|
813
|
-
if (ctrl) {
|
|
814
|
-
const streams = [ctrl.statusChanges, ctrl.valueChanges].filter((s) => !!s);
|
|
815
|
-
if (streams.length) {
|
|
816
|
-
merge(...streams)
|
|
817
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
818
|
-
.subscribe(() => this._ngControlRev.update((n) => n + 1));
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
const submit = this.parentFormGroup?.ngSubmit ?? this.parentForm?.ngSubmit;
|
|
822
|
-
if (submit) {
|
|
823
|
-
submit
|
|
824
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
825
|
-
.subscribe(() => this._formSubmitRev.update((n) => n + 1));
|
|
826
|
-
}
|
|
821
|
+
this.errorWiring.connect();
|
|
827
822
|
}
|
|
828
823
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: TransferComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
829
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: TransferComponent, isStandalone: true, selector: "tw-transfer", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, keyFn: { classPropertyName: "keyFn", publicName: "keyFn", isSignal: true, isRequired: true, transformFunction: null }, labelFn: { classPropertyName: "labelFn", publicName: "labelFn", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, display: { classPropertyName: "display", publicName: "display", isSignal: true, isRequired: false, transformFunction: null }, behavior: { classPropertyName: "behavior", publicName: "behavior", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, requiredInput: { classPropertyName: "requiredInput", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, idInput: { classPropertyName: "idInput", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedby: { classPropertyName: "ariaDescribedby", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", moved: "moved" }, host: { attributes: { "role": "group" }, properties: { "id": "id()", "class": "rootClasses()", "attr.aria-label": "ariaLabel() || null", "attr.aria-labelledby": "resolvedLabelledBy() || null", "attr.aria-describedby": "resolvedDescribedBy() || null", "attr.aria-disabled": "disabled() || null", "attr.
|
|
824
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: TransferComponent, isStandalone: true, selector: "tw-transfer", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, keyFn: { classPropertyName: "keyFn", publicName: "keyFn", isSignal: true, isRequired: true, transformFunction: null }, labelFn: { classPropertyName: "labelFn", publicName: "labelFn", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, display: { classPropertyName: "display", publicName: "display", isSignal: true, isRequired: false, transformFunction: null }, behavior: { classPropertyName: "behavior", publicName: "behavior", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, requiredInput: { classPropertyName: "requiredInput", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, idInput: { classPropertyName: "idInput", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedby: { classPropertyName: "ariaDescribedby", publicName: "aria-describedby", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", moved: "moved" }, host: { attributes: { "role": "group" }, properties: { "id": "id()", "class": "rootClasses()", "attr.aria-label": "ariaLabel() || null", "attr.aria-labelledby": "resolvedLabelledBy() || null", "attr.aria-describedby": "resolvedDescribedBy() || null", "attr.aria-disabled": "disabled() || null", "attr.data-focused": "focused() || null" } }, providers: [
|
|
830
825
|
{
|
|
831
826
|
provide: TW_FORM_FIELD_CONTROL,
|
|
832
827
|
useExisting: forwardRef(() => TransferComponent),
|
|
@@ -878,6 +873,7 @@ class TransferComponent extends FormFieldControl {
|
|
|
878
873
|
[class]="listClasses()"
|
|
879
874
|
[style.height.px]="listHeightPx()"
|
|
880
875
|
[attr.aria-labelledby]="panel.titleId"
|
|
876
|
+
[attr.aria-invalid]="panel.side === 'target' && errorState() ? 'true' : null"
|
|
881
877
|
(cdkListboxValueChange)="onCheckedChange(panel.side, $event)"
|
|
882
878
|
>
|
|
883
879
|
@for (row of panel.rows; track row.key) {
|
|
@@ -911,7 +907,16 @@ class TransferComponent extends FormFieldControl {
|
|
|
911
907
|
}
|
|
912
908
|
</div>
|
|
913
909
|
} @else {
|
|
914
|
-
|
|
910
|
+
<!--
|
|
911
|
+
Also carries aria-invalid: an empty target is exactly when a required
|
|
912
|
+
transfer is invalid, and the listbox above does not exist to carry it
|
|
913
|
+
(an empty role=listbox would violate aria-required-children).
|
|
914
|
+
-->
|
|
915
|
+
<div
|
|
916
|
+
[class]="emptyClasses()"
|
|
917
|
+
[style.height.px]="listHeightPx()"
|
|
918
|
+
[attr.aria-invalid]="panel.side === 'target' && errorState() ? 'true' : null"
|
|
919
|
+
>{{ panel.emptyText }}</div>
|
|
915
920
|
}
|
|
916
921
|
</div>
|
|
917
922
|
</ng-template>
|
|
@@ -984,7 +989,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
984
989
|
'[attr.aria-labelledby]': 'resolvedLabelledBy() || null',
|
|
985
990
|
'[attr.aria-describedby]': 'resolvedDescribedBy() || null',
|
|
986
991
|
'[attr.aria-disabled]': 'disabled() || null',
|
|
987
|
-
|
|
992
|
+
// NOTE: no `aria-invalid` here. ARIA 1.2 does not allow it on `role="group"`
|
|
993
|
+
// (axe: critical `aria-allowed-attr`). It is carried by the target panel's
|
|
994
|
+
// content region instead — `value` is `targetKeys`, so that panel is the one
|
|
995
|
+
// holding the value. `tags-input` and `file-upload` were corrected the same
|
|
996
|
+
// way in an earlier pass; this component was missed.
|
|
988
997
|
'[attr.data-focused]': 'focused() || null',
|
|
989
998
|
},
|
|
990
999
|
template: `
|
|
@@ -1034,6 +1043,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
1034
1043
|
[class]="listClasses()"
|
|
1035
1044
|
[style.height.px]="listHeightPx()"
|
|
1036
1045
|
[attr.aria-labelledby]="panel.titleId"
|
|
1046
|
+
[attr.aria-invalid]="panel.side === 'target' && errorState() ? 'true' : null"
|
|
1037
1047
|
(cdkListboxValueChange)="onCheckedChange(panel.side, $event)"
|
|
1038
1048
|
>
|
|
1039
1049
|
@for (row of panel.rows; track row.key) {
|
|
@@ -1067,7 +1077,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
1067
1077
|
}
|
|
1068
1078
|
</div>
|
|
1069
1079
|
} @else {
|
|
1070
|
-
|
|
1080
|
+
<!--
|
|
1081
|
+
Also carries aria-invalid: an empty target is exactly when a required
|
|
1082
|
+
transfer is invalid, and the listbox above does not exist to carry it
|
|
1083
|
+
(an empty role=listbox would violate aria-required-children).
|
|
1084
|
+
-->
|
|
1085
|
+
<div
|
|
1086
|
+
[class]="emptyClasses()"
|
|
1087
|
+
[style.height.px]="listHeightPx()"
|
|
1088
|
+
[attr.aria-invalid]="panel.side === 'target' && errorState() ? 'true' : null"
|
|
1089
|
+
>{{ panel.emptyText }}</div>
|
|
1071
1090
|
}
|
|
1072
1091
|
</div>
|
|
1073
1092
|
</ng-template>
|