@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,18 +1,23 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, makeEnvironmentProviders, Directive, input, contentChildren, inject, DestroyRef, viewChild, signal,
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders, Directive, input, contentChildren, inject, DestroyRef, viewChild, computed, signal, afterRenderEffect, untracked, ChangeDetectionStrategy, Component, contentChild } from '@angular/core';
|
|
3
3
|
import { NgTemplateOutlet } from '@angular/common';
|
|
4
4
|
import { Directionality } from '@angular/cdk/bidi';
|
|
5
5
|
import { tv } from 'tailwind-variants';
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Default English labels used when `TW_TIMELINE_SCROLL_LABELS` is not provided.
|
|
9
|
+
* Typed `Required<TwTimelineScrollLabels>` so readers keep a non-optional
|
|
10
|
+
* `string` for every key even though the interface itself is all-optional.
|
|
11
|
+
*/
|
|
8
12
|
const DEFAULT_TW_TIMELINE_SCROLL_LABELS = {
|
|
9
13
|
scrollPrevious: 'Scroll to previous events',
|
|
10
14
|
scrollNext: 'Scroll to next events',
|
|
15
|
+
scrollRegion: 'Timeline events',
|
|
11
16
|
};
|
|
12
17
|
/**
|
|
13
18
|
* Injection token carrying localisable `aria-label` strings for the timeline's
|
|
14
|
-
* horizontal-overflow
|
|
15
|
-
*
|
|
19
|
+
* horizontal-overflow affordances — the prev/next chevron buttons and the
|
|
20
|
+
* scrollable viewport between them. They do not affect any other rendered text.
|
|
16
21
|
*
|
|
17
22
|
* Provide via `provideTwTimelineScrollLabels({ ... })` at the root or feature level.
|
|
18
23
|
* If both keys are present they override the English defaults; if only one is present
|
|
@@ -101,6 +106,17 @@ const timelineVariants = tv({
|
|
|
101
106
|
// column and stretches through this padding to touch the next item's
|
|
102
107
|
// marker — items butt against each other with no container `gap-*` so
|
|
103
108
|
// the line stays continuous.
|
|
109
|
+
//
|
|
110
|
+
// `item: gap-*` is NOT that vertical separation — it is the gutter
|
|
111
|
+
// between the marker column and the body (a column gap in vertical
|
|
112
|
+
// orientation, the subgrid row gap in horizontal). It runs past
|
|
113
|
+
// CLAUDE.md's `gap-3` ceiling because that scale governs spacing
|
|
114
|
+
// *inside* an element (icon↔label, button groups) against a fixed
|
|
115
|
+
// ~16–20px glyph, whereas this gutter is measured against a marker
|
|
116
|
+
// column that itself scales `w-6`→`w-12`. Held at `gap-3`, an `xl`
|
|
117
|
+
// 48px bubble sits 12px from its text — the body reads as welded to
|
|
118
|
+
// the marker and every step above `md` becomes visually identical.
|
|
119
|
+
// The gutter therefore tracks the column: 12/16/20/24/32px.
|
|
104
120
|
xs: { item: 'gap-3', body: 'text-xs gap-1 pb-3', timestamp: 'text-2xs', number: 'text-2xs' },
|
|
105
121
|
sm: { item: 'gap-4', body: 'text-sm gap-1 pb-4', timestamp: 'text-xs', number: 'text-xs' },
|
|
106
122
|
md: { item: 'gap-5', body: 'text-sm gap-1.5 pb-6', timestamp: 'text-xs', number: 'text-xs' },
|
|
@@ -164,6 +180,17 @@ const timelineVariants = tv({
|
|
|
164
180
|
{ marker: 'circle', size: 'xs', class: { marker: 'size-6' } },
|
|
165
181
|
{ marker: 'circle', size: 'sm', class: { marker: 'size-7' } },
|
|
166
182
|
{ marker: 'circle', size: 'md', class: { marker: 'size-8' } },
|
|
183
|
+
// `size-10` / `size-12` sit above the glyph scale's `size-10` ceiling and
|
|
184
|
+
// outside the square-interactive scale (which stops at `size-9`), and
|
|
185
|
+
// neither scale governs here: the circle marker is a *container* — it
|
|
186
|
+
// wraps a projected `[twTimelineMarker]` icon/avatar or the auto-number —
|
|
187
|
+
// and the item host is never focusable, so it is not a hit target either.
|
|
188
|
+
// A container has to out-measure its contents: `<tw-icon size="lg">` is
|
|
189
|
+
// `size-6` (24px) and `size="xl"` is `size-8` (32px), so capping the bubble
|
|
190
|
+
// at the `size-9` (36px) hit-target ceiling would leave a 6px rim at `lg`
|
|
191
|
+
// and a 2px rim at `xl` — the glyph reads as cropped and the ring/border
|
|
192
|
+
// states have nowhere to render. `size-10` / `size-12` hold a constant 8px
|
|
193
|
+
// inset at both steps instead.
|
|
167
194
|
{ marker: 'circle', size: 'lg', class: { marker: 'size-10' } },
|
|
168
195
|
{ marker: 'circle', size: 'xl', class: { marker: 'size-12' } },
|
|
169
196
|
// Vertical-only: `mt-*` nudges the small dot down so its vertical center
|
|
@@ -354,9 +381,13 @@ function resolveConnectorClasses(state, color, lineStyle) {
|
|
|
354
381
|
// to clientWidth, the overflow check fails permanently, and the chevrons
|
|
355
382
|
// never enable. The per-density min-w-* on each item still establishes the
|
|
356
383
|
// floor.
|
|
384
|
+
// The viewport carries `tabindex="0"` so keyboard-only users can reach and
|
|
385
|
+
// arrow-scroll it (axe: scrollable-region-focusable), which makes it a
|
|
386
|
+
// focusable element and therefore obliges it to carry the canonical focus ring.
|
|
357
387
|
const HORIZONTAL_VIEWPORT_CLASSES = 'tw-scrollbar-none grid grid-flow-col auto-cols-max ' +
|
|
358
388
|
'[grid-template-rows:minmax(0,1fr)_auto] ' +
|
|
359
|
-
'overflow-x-auto scroll-smooth motion-reduce:scroll-auto w-full'
|
|
389
|
+
'overflow-x-auto scroll-smooth motion-reduce:scroll-auto w-full ' +
|
|
390
|
+
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500';
|
|
360
391
|
// ── Chevron button class string ──
|
|
361
392
|
// Shared by both overflow chevron buttons. Positioned absolutely against the
|
|
362
393
|
// timeline host (which carries `relative` in horizontal orientation via the
|
|
@@ -480,26 +511,36 @@ class TimelineComponent {
|
|
|
480
511
|
_scrollLabelsOverride = inject(TW_TIMELINE_SCROLL_LABELS, {
|
|
481
512
|
optional: true,
|
|
482
513
|
});
|
|
483
|
-
/**
|
|
484
|
-
|
|
514
|
+
/**
|
|
515
|
+
* @internal Live `dir` from CDK `Directionality`. Read straight from
|
|
516
|
+
* `valueSignal` (CDK 22+) so `_isRtl` re-evaluates on a runtime `dir` flip
|
|
517
|
+
* with no manual subscription mirror.
|
|
518
|
+
*/
|
|
519
|
+
_cdkDir = computed(() => this._directionality?.valueSignal() ?? 'ltr', /* @ts-ignore */
|
|
485
520
|
...(ngDevMode ? [{ debugName: "_cdkDir" }] : /* istanbul ignore next */ []));
|
|
486
521
|
/** @internal True when the host renders inside a `dir="rtl"` ancestor. */
|
|
487
522
|
_isRtl = computed(() => this._cdkDir() === 'rtl', /* @ts-ignore */
|
|
488
523
|
...(ngDevMode ? [{ debugName: "_isRtl" }] : /* istanbul ignore next */ []));
|
|
489
524
|
/**
|
|
490
|
-
* @internal Defaults to `false` until the first measurement runs in
|
|
491
|
-
* `
|
|
492
|
-
* where the buttons appear live before the
|
|
525
|
+
* @internal Defaults to `false` until the first measurement runs in the
|
|
526
|
+
* `afterRenderEffect` in the constructor. This avoids a one-frame
|
|
527
|
+
* "enabled-then-disabled" flash where the buttons appear live before the
|
|
528
|
+
* scroll state has been read.
|
|
493
529
|
*/
|
|
494
530
|
_canScrollPrev = signal(false, /* @ts-ignore */
|
|
495
531
|
...(ngDevMode ? [{ debugName: "_canScrollPrev" }] : /* istanbul ignore next */ []));
|
|
496
532
|
_canScrollNext = signal(false, /* @ts-ignore */
|
|
497
533
|
...(ngDevMode ? [{ debugName: "_canScrollNext" }] : /* istanbul ignore next */ []));
|
|
498
534
|
/** @internal Resolved label record. Falls back to English defaults for any missing key. */
|
|
499
|
-
scrollLabels = computed(() =>
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
535
|
+
scrollLabels = computed(() => {
|
|
536
|
+
// Drop explicitly-undefined keys before merging. A plain spread would let
|
|
537
|
+
// `{ scrollRegion: undefined }` overwrite the default with undefined,
|
|
538
|
+
// which reaches the template as a null `aria-label` and silently strips
|
|
539
|
+
// the scroll region's accessible name. `Required<>` then guarantees the
|
|
540
|
+
// template a string for every label.
|
|
541
|
+
const override = Object.fromEntries(Object.entries(this._scrollLabelsOverride ?? {}).filter(([, v]) => v !== undefined));
|
|
542
|
+
return { ...DEFAULT_TW_TIMELINE_SCROLL_LABELS, ...override };
|
|
543
|
+
}, /* @ts-ignore */
|
|
503
544
|
...(ngDevMode ? [{ debugName: "scrollLabels" }] : /* istanbul ignore next */ []));
|
|
504
545
|
/** @internal True when the prev button should be aria-hidden + visually suppressed. */
|
|
505
546
|
_prevButtonHidden = computed(() => {
|
|
@@ -557,21 +598,45 @@ class TimelineComponent {
|
|
|
557
598
|
}
|
|
558
599
|
_resizeObserver = null;
|
|
559
600
|
constructor() {
|
|
560
|
-
// The viewport
|
|
561
|
-
//
|
|
562
|
-
|
|
601
|
+
// The horizontal viewport is gated behind `@if (orientation() ===
|
|
602
|
+
// 'horizontal')`, so scroll detection has to re-run whenever orientation
|
|
603
|
+
// is (re)committed — not just once at mount. The previous
|
|
604
|
+
// `afterNextRender(() => this._setupScrollDetection())` fired a single
|
|
605
|
+
// time, and `_setupScrollDetection()` early-returns when the orientation
|
|
606
|
+
// is vertical: a timeline mounted vertical and later flipped to horizontal
|
|
607
|
+
// (`[orientation]="isWide() ? 'horizontal' : 'vertical'"`) never got a
|
|
608
|
+
// ResizeObserver and never measured, so `_canScrollPrev`/`_canScrollNext`
|
|
609
|
+
// stayed `false` and both chevrons were permanently invisible AND
|
|
610
|
+
// disabled. Same bug class `carousel.ts` records having already fixed.
|
|
611
|
+
//
|
|
612
|
+
// `afterRenderEffect` is the right primitive rather than `effect()`: the
|
|
613
|
+
// work is a DOM *measurement* that must observe the embedded view holding
|
|
614
|
+
// the viewport, and a plain `effect()` runs before this cycle's embedded
|
|
615
|
+
// views are refreshed. Tracking `_viewportRef()` as well as `orientation()`
|
|
616
|
+
// makes it self-healing — if the view query has not resolved on the pass
|
|
617
|
+
// that commits `'horizontal'`, the effect re-runs when it does.
|
|
618
|
+
//
|
|
619
|
+
// The body is wrapped in `untracked()` so the tracked region is exactly
|
|
620
|
+
// `{ orientation, _viewportRef }`: `_setupScrollDetection()` writes
|
|
621
|
+
// `_canScrollPrev`/`_canScrollNext`, and reading them here would be a
|
|
622
|
+
// read-write cycle of the shape CLAUDE.md bans.
|
|
623
|
+
afterRenderEffect(() => {
|
|
624
|
+
this.orientation();
|
|
625
|
+
this._viewportRef();
|
|
626
|
+
untracked(() => this._setupScrollDetection());
|
|
627
|
+
});
|
|
563
628
|
this._destroyRef.onDestroy(() => {
|
|
564
629
|
this._resizeObserver?.disconnect();
|
|
565
630
|
this._resizeObserver = null;
|
|
566
631
|
});
|
|
567
|
-
if (this._directionality) {
|
|
568
|
-
const sub = this._directionality.change.subscribe((value) => {
|
|
569
|
-
this._cdkDir.set(value);
|
|
570
|
-
});
|
|
571
|
-
this._destroyRef.onDestroy(() => sub.unsubscribe());
|
|
572
|
-
}
|
|
573
632
|
}
|
|
574
633
|
_setupScrollDetection() {
|
|
634
|
+
// Re-entrant: this runs on every orientation/viewport change, so drop any
|
|
635
|
+
// observer bound to the previous (now detached) viewport element first.
|
|
636
|
+
// Done before the early return so flipping back to vertical also releases
|
|
637
|
+
// it. `_destroyRef.onDestroy` still owns the final teardown.
|
|
638
|
+
this._resizeObserver?.disconnect();
|
|
639
|
+
this._resizeObserver = null;
|
|
575
640
|
// Vertical orientation never renders the viewport — nothing to do.
|
|
576
641
|
if (this.orientation() !== 'horizontal')
|
|
577
642
|
return;
|
|
@@ -668,7 +733,7 @@ class TimelineComponent {
|
|
|
668
733
|
state,
|
|
669
734
|
}).number();
|
|
670
735
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: TimelineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
671
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: TimelineComponent, isStandalone: true, selector: "tw-timeline", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, lineStyle: { classPropertyName: "lineStyle", publicName: "lineStyle", isSignal: true, isRequired: false, transformFunction: null }, scrollControls: { classPropertyName: "scrollControls", publicName: "scrollControls", isSignal: true, isRequired: false, transformFunction: null } }, host: {
|
|
736
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: TimelineComponent, isStandalone: true, selector: "tw-timeline", inputs: { orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, lineStyle: { classPropertyName: "lineStyle", publicName: "lineStyle", isSignal: true, isRequired: false, transformFunction: null }, scrollControls: { classPropertyName: "scrollControls", publicName: "scrollControls", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.role": "orientation() === \"horizontal\" ? null : \"list\"", "class": "rootClasses()" } }, queries: [{ propertyName: "items", predicate: TimelineItemComponent, isSignal: true }], viewQueries: [{ propertyName: "_viewportRef", first: true, predicate: ["scrollViewport"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
672
737
|
<ng-template #itemsTpl>
|
|
673
738
|
<ng-content />
|
|
674
739
|
</ng-template>
|
|
@@ -696,6 +761,9 @@ class TimelineComponent {
|
|
|
696
761
|
</button>
|
|
697
762
|
<div
|
|
698
763
|
#scrollViewport
|
|
764
|
+
role="list"
|
|
765
|
+
tabindex="0"
|
|
766
|
+
[attr.aria-label]="scrollLabels().scrollRegion"
|
|
699
767
|
[class]="horizontalViewportClasses"
|
|
700
768
|
(scroll)="_onScroll()"
|
|
701
769
|
>
|
|
@@ -733,9 +801,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
733
801
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
734
802
|
imports: [NgTemplateOutlet],
|
|
735
803
|
host: {
|
|
736
|
-
role
|
|
804
|
+
// The `list` role lives on the host only in vertical orientation, where the
|
|
805
|
+
// projected `tw-timeline-item` elements (role="listitem") are its direct
|
|
806
|
+
// children. In horizontal orientation the host also owns the two scroll
|
|
807
|
+
// chevrons, which are not `listitem`s — a `list` here would own children its
|
|
808
|
+
// role forbids (axe: aria-required-children). The role moves to the scroll
|
|
809
|
+
// viewport instead, which does contain exactly the items.
|
|
810
|
+
//
|
|
811
|
+
// `aria-orientation` is deliberately absent: `list` does not support it
|
|
812
|
+
// (axe: aria-allowed-attr), and the axis is a purely visual decision that
|
|
813
|
+
// carries no meaning for assistive tech.
|
|
814
|
+
'[attr.role]': 'orientation() === "horizontal" ? null : "list"',
|
|
737
815
|
'[class]': 'rootClasses()',
|
|
738
|
-
'[attr.aria-orientation]': 'orientation() === "horizontal" ? "horizontal" : null',
|
|
739
816
|
},
|
|
740
817
|
// The projected items live inside a single `<ng-template #itemsTpl>` and are
|
|
741
818
|
// outletted into either the horizontal viewport or the vertical pass-through.
|
|
@@ -772,6 +849,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
|
|
|
772
849
|
</button>
|
|
773
850
|
<div
|
|
774
851
|
#scrollViewport
|
|
852
|
+
role="list"
|
|
853
|
+
tabindex="0"
|
|
854
|
+
[attr.aria-label]="scrollLabels().scrollRegion"
|
|
775
855
|
[class]="horizontalViewportClasses"
|
|
776
856
|
(scroll)="_onScroll()"
|
|
777
857
|
>
|
|
@@ -981,7 +1061,10 @@ class TimelineItemComponent {
|
|
|
981
1061
|
if (this.timeline.orientation() === 'horizontal')
|
|
982
1062
|
return '';
|
|
983
1063
|
// Pin the column to the marker's own width so siblings stay aligned even
|
|
984
|
-
// when items render slightly different bubble content.
|
|
1064
|
+
// when items render slightly different bubble content. These mirror the
|
|
1065
|
+
// circle-marker diameters one-for-one (`size-6`…`size-12` above) — the
|
|
1066
|
+
// `w-10`/`w-12` steps are off-scale for the same reason the bubbles are,
|
|
1067
|
+
// and diverging here would knock every marker off the connector's axis.
|
|
985
1068
|
switch (this.timeline.size()) {
|
|
986
1069
|
case 'xs':
|
|
987
1070
|
return 'w-6';
|